Finite-dimensional Hilbert spaces

Class hilbert_space is an abstraction of a finite-dimensional 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\),

\[\mathcal{H} = \mathcal{H}_1 \otimes \mathcal{H}_2 \otimes \ldots \otimes \mathcal{H}_N.\]

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 two-dimensional (\(b = 1\)); It is spanned by the occupation number states \(|0\rangle\) and \(|1\rangle\). For a spin-1 operator, we have a 4-dimensional 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 infinite-dimensional 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,

\[|n\rangle_\mathcal{H} = |n_1\rangle_{\mathcal{H}_1} \otimes |n_2\rangle_{\mathcal{H}_2} \otimes\ldots\otimes |n_N\rangle_{\mathcal{H}_N}.\]

In the code, the basis vectors are represented by 64-bit 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 the 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 7-62 are unused – set to zero. The most significant bit 63 is reserved and can never be occupied by an elementary space. This restriction is necessary to make sure that the size of a Hilbert space with all the allowed 63 bits used up is still representable by a value of the type sv_index_type.

../_images/basis_state.svg

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 4-dimensional 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)   // Spin-1/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 of elementary_space and/or libcommute::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 exceed the 63-bit limit 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 exceed the 63-bit limit of the basis state index.

Copy/move-constructors 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. Throws hilbert_space_too_big if adding es into the product would exceed the 63-bit limit 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.

sv_index_type dim() const
friend sv_index_type 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 63 bits.

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 of hilbert_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/move-constructors, 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 copy-constructor. 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

Read-only 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\)).

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 two-dimensional (\(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.

template<typename ...Args>
elementary_space_boson(int n_bits, Args&&... indices)

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 ...Args>
elementary_space_spin(double spin, Args&&... indices)

Construct a spin elementary space with a given spin \(S\) = spin.

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 user-defined 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 follows

    template<> 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 for my_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 and my_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 single-ID 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.