State vectors

StateVector concept

Let us say we want to make type SV a libcommute-compatible state vector type so that linear operators can act on instances of SV. For this we have to make SV model the StateVector concept. In a nutshell, a StateVector type is a one-dimensional array of numbers (quantum amplitudes) allowing integer indexing and implementing a certain interface. Elements of the array do not have to be stored contiguously. Acceptable index values must be at least 64-bit wide unsigned integers since libcommute uses the following type for basis state indexing.

using sv_index_type = std::uint64_t

Defined in <libcommute/loperator/state_vector.hpp>

Type of basis state indices.

The table below shows the interface (a set of free functions and a metafunction) that needs be implemented for an object sv of type SV.

libcommute provides an implementation of the StateVector concept for std::vector (see <libcommute/loperator/state_vector.hpp>).

Function/metafunction

Description

Implementation for std::vector<T>

element_type<SV>::type

Type of the elements.

T

get_element(sv, n)

Return the n-th element of sv.

sv[n]

update_add_element(sv, n, value)

Add a value of some type U to the n-th element of sv.

sv[n] += value or sv[n] = sv[n] + value

The compound-assignment from type U will be used whenever sv’s elements support it. Otherwise, the implementation will fall back to the simple addition.

set_zeros(sv)

Fill sv with zeros.

std::fill(sv.begin(), sv.end(), zero).

The zero value is created by make_const(0) as described in “Custom scalar types”.

zeros_like(sv)

Return an object of the same type and size as sv but filled with zeros.

Creates a new object as std::vector<T>(sv.size(), zero).

foreach(sv, f)

Apply a function-like object f to all basis state index/non-zero element pairs (n, a) in sv.

In a for-loop, calls f(n, a) for all non-zero elements a as detected by is_zero() (see “Custom scalar types”).

Inclusion of <libcommute/loperator/state_vector_eigen3.hpp> makes some Eigen 3 types (column vectors, vector blocks, column-like matrix blocks and one-dimensional Eigen::Map views) compatible with the StateVector concept as well.

Sparse state vector

sparse_state_vector is a state vector that saves memory by storing only the non-zero elements. It is essentially a wrapper around std::unordered_map modelling the StateVector concept. Here, we show only the part of its interface not covered by StateVector.

template<typename ScalarType>
class sparse_state_vector

State vector with a sparse storage of elements (quantum amplitudes). ScalarType is the type of the elements.

sparse_state_vector() = delete
sparse_state_vector(sv_index_type size)

Construct a zero (empty) sparse vector with a given size – dimension of the corresponding Hilbert space.

sv_index_type size() const

Size (dimension) of the vector.

ScalarType &operator[](sv_index_type n)

Access the n-th element. If it is zero (missing from the storage), then a new value-initialized element will be inserted and a reference to it will be returned.

Warning

Improper use of this method may result in zero elements being stored in the unordered map. Only the non-zero values should be assigned to the references returned by it.

sv_index_type n_nonzeros() const

Get the number of non-zero (stored) elements.

void prune()

Remove all zero elements (as defined by scalar_traits<ScalarType>::is_zero()) from the unordered map.

template<typename UnaryPredicate>
void prune(UnaryPredicate &&p)

Remove unordered map elements (amplitudes) for which predicate p returns true.

Mapped basis view

mapped_basis_view is another utility type modelling the StateVector concept. It is a view of a state vector, which translates basis state index arguments of get_element() and update_add_element() according to a predefined map sv_index_type -> sv_index_type. The element access functions throw std::out_of_range if their index argument is missing from the map.

mapped_basis_view can be used in situations where a linear operator acts in a small subspace of a full Hilbert space, and it is desirable to store vector components only within that subspace. Such a situation naturally emerges when working with invariant subspaces of operators.

template<typename StateVector, bool Ref = true>
class mapped_basis_view

View of a StateVector object that translates basis state indices according to a certain mapping.

StateVector - type of the underlying state vector object. Defining a read-only view (such that prohibits update_add_element() operations) requires using a const-qualified type here. For example, one can use StateVector = std::vector<double> for a read-write view, and StateVector = const std::vector<double> for a read-only view.

Ref - by default, mapped_basis_view stores a reference to the underlying state vector. Setting this option to false will result in a copy being created and stored instead. This feature can be useful when the underlying type is already a view-like object similar to Eigen::Map.

The mapped basis views should always be constructed by means of a special factory class basis_mapper and its methods basis_mapper:: make_view()/basis_mapper::make_const_view().

class basis_mapper

Factory class for mapped_basis_view.

Constructors

basis_mapper(std::vector<sv_index_type> const &basis_state_indices)

Build a mapping from a list of basis states basis_state_indices to their positions within the list.

std::vector<sv_index_type> basis_indices{3, 5, 6};
basis_mapper mapper(basis_indices);

// Views created by 'mapper' will translate basis state indices
// according to
// 0 -> std::out_of_range
// 1 -> std::out_of_range
// 2 -> std::out_of_range
// 3 -> 0
// 4 -> std::out_of_range
// 5 -> 1
// 6 -> 2
// 7 -> std::out_of_range
// ...
template<typename HSType, typename LOpScalarType, int... LOpAlgebraIDs>
basis_mapper(loperator<LOpScalarType, LOpAlgebraIDs...> const &O, HSType const &hs)

Build a mapping from a set of all basis states contributing to \(\hat O|0\rangle\).

Operator O acts in the Hilbert space hs. \(|0\rangle\) is the basis state with index 0 (‘vacuum’ state in the case of fermions and bosons). Mapped values are assigned continuously starting from 0 without any specific order.

template<typename HSType, typename LOpScalarType, int... LOpAlgebraIDs>
basis_mapper(std::vector<loperator<LOpScalarType, LOpAlgebraIDs...>> const &O_list, HSType const &hs, unsigned int N)

Given a list of operators \(\{\hat O_1, \hat O_2, \hat O_3, \ldots, \hat O_M\}\), build a mapping from all basis states contributing to all states \(\hat O_1^{n_1} \hat O_2^{n_2} \ldots \hat O_M^{n_M} |0\rangle\), where \(n_m \geq 0\) and \(\sum_{m=1}^M n_M = N\).

Operators in O_list act in the Hilbert space hs. \(|0\rangle\) is the basis state with index 0 (‘vacuum’ state in the case of fermions and bosons). Mapped values are assigned continuously starting from 0 without any specific order.

This constructor is useful to create a mapping from a fixed-particle-number subspace of a fermionic/bosonic Hilbert space.

mapped_basis_view factory functions

template<typename StateVector>
mapped_basis_view<StateVector> make_view(StateVector &&sv) const
template<typename StateVector>
mapped_basis_view<StateVector const> make_const_view(StateVector &&sv) const

Make a read/write or constant view of sv. Constant views will not be accepted by update_add_element(). If sv is not an lvalue reference, the resulting view will hold a copy of sv.

Warning

To reduce memory footprint, mapped_basis_view objects store a reference to the basis index map owned by their parent basis_mapper object. For this reason, the views should never outlive the mapper.

Other methods

sv_index_type size() const

Number of elements in the index map.

std::unordered_map<sv_index_type, sv_index_type> const &map() const

Direct access to the underlying index map.

std::unordered_map<sv_index_type, sv_index_type> inverse_map() const

Build and return an inverse index map. Depending on map’s size, building the inverse can be an expensive operation. Calling this method on a non-invertible map is undefined behavior.

N-fermion sector views

There are two more specialized flavours of the basis mapping views called \(N\)-fermion sector views and \(N\)-fermion multisector views. They can come in handy when working with particle-number preserving models of fermions. If the model is large, then generating and storing a basis state index map for mapped_basis_view may become too expensive.

template<typename StateVector, bool Ref = true>
class n_fermion_sector_view

Defined in <libcommute/loperator/n_fermion_sector_view.hpp>

View of a StateVector object that translates basis state indices from a full Hilbert space to its subspace (sector) with a fixed total occupation of fermionic degrees of freedom \(N\). The full Hilbert space does not have to be purely fermionic.

n_fermion_sector_view is generally less performant than mapped_basis_view in terms of the index translation speed. However, its required storage space scales only as \(O(M \min(N, M - N))\), where \(M\) is the total number of the fermionic degrees of freedom. This scaling law is much milder that the exponential growth of the sector size.

StateVector - type of the underlying state vector object. Defining a read-only view (such that prohibits update_add_element() operations) requires using a const-qualified type here. For example, one can use StateVector = std::vector<double> for a read-write view, and StateVector = const std::vector<double> for a read-only view.

Ref - by default, n_fermion_sector_view stores a reference to the underlying state vector. Setting this option to false will result in a copy being created and stored instead. This feature can be useful when the underlying type is already a view-like object similar to Eigen::Map.

template<typename SV, typename HSType>
n_fermion_sector_view(SV &&sv, HSType const &hs, unsigned int N)

Construct a view of the state vector sv, defined in the N-fermion sector of the full Hilbert space hs.

sv_index_type map_index(sv_index_type index) const

Translate a basis state index from the full Hilbert space to the sector.

template<typename HSType>
struct sector_descriptor

Description of an \(N\)-fermion sector defined over a subset of fermionic degrees of freedom.

HSType - type of the full Hilbert space this sector belongs to.

std::set<typename HSType::index_types> indices

Set of indices corresponding to the relevant fermionic degrees of freedom.

unsigned int N

Total occupation of the sector.

template<typename StateVector, bool Ref = true>
class n_fermion_multisector_view

Defined in <libcommute/loperator/n_fermion_sector_view.hpp>

View of a StateVector object that translates basis state indices from a full Hilbert space to an \(N\)-fermion multisector. A multisector is a set of all basis states, which have \(N_1\) particles within a subset of fermionic modes \(\{S_1\}\), \(N_2\) particles within another subset \(\{S_2\}\) and so on. There can be any number of individual pairs \((\{S_i\}, N_i)\) (sectors contributing to the multisector) as long as all subsets \(\{S_i\}\) are disjoint. The full Hilbert space does not have to be purely fermionic.

n_fermion_multisector_view is generally less performant than mapped_basis_view in terms of the index translation speed. However, its required storage space scales only as \(O(\sum_i M_i \min(N_i, M_i - N_i))\), where \(M_i = |\{S_i\}|\). This scaling law is much milder that the exponential growth of the multisector size.

It is advised to use n_fermion_sector_view instead, if there is only one contributing sector that also spans all fermionic degrees of freedom.

StateVector - type of the underlying state vector object. Defining a read-only view (such that prohibits update_add_element() operations) requires using a const-qualified type here. For example, one can use StateVector = std::vector<double> for a read-write view, and StateVector = const std::vector<double> for a read-only view.

Ref - by default, n_fermion_multisector_view stores a reference to the underlying state vector. Setting this option to false will result in a copy being created and stored instead. This feature can be useful when the underlying type is already a view-like object similar to Eigen::Map.

template<typename SV, typename HSType>
n_fermion_multisector_view(SV &&sv, HSType const &hs, std::vector<sector_descriptor<HSType>> const &sectors)

Construct a view of the state vector sv, defined in the \(N\)-fermion multisector of the full Hilbert space hs. The multisector is defined via a list of contributing sectors (list of \((\{S_i\}, N_i)\) pairs).

sv_index_type map_index(sv_index_type index) const

Translate a basis state index from the full Hilbert space to the multisector.

Besides n_fermion_sector_view and n_fermion_multisector_view, <libcommute/loperator/n_fermion_sector_view.hpp> defines a few supplemental utility functions that help working with (multi)sectors.

template<typename StateVector, typename HSType>
auto make_nfs_view(StateVector &&sv, HSType const &hs, unsigned int N)
template<typename StateVector, typename HSType>
auto make_const_nfs_view(StateVector &&sv, HSType const &hs, unsigned int N)

Make and return a read/write or constant N-fermion sector view of sv within the full Hilbert space hs. If sv is not an lvalue reference, the resulting view will hold a copy of sv.

template<typename StateVector, typename HSType>
auto make_nfms_view(StateVector &&sv, HSType const &hs, std::vector<sector_descriptor<HSType>> const &sectors)
template<typename StateVector, typename HSType>
auto make_const_nfms_view(StateVector &&sv, HSType const &hs, std::vector<sector_descriptor<HSType>> const &sectors)

Make and return a read/write or constant \(N\)-fermion multisector view of sv within the full Hilbert space hs. The multisector is defined via a list of contributing sectors (list of \((\{S_i\}, N_i)\) pairs). If sv is not an lvalue reference, the resulting view will hold a copy of sv.

template<typename HSType>
sv_index_type n_fermion_sector_size(HSType const &hs, unsigned int N)

Size of the N-fermion sector within the full Hilbert space hs.

template<typename HSType>
sv_index_type n_fermion_multisector_size(HSType const &hs, std::vector<sector_descriptor<HSType>> const &sectors)

Size of the \(N\)-fermion multisector within the full Hilbert space hs. The multisector is defined via a list of contributing sectors (list of \((\{S_i\}, N_i)\) pairs).

template<typename HSType>
std::vector<sv_index_type> n_fermion_sector_basis_states(HSType const &hs, unsigned int N)

Build and return a list of basis state indices forming the N-fermion sector within the full Hilbert space hs. The order of the indices in the list is consistent with the results of n_fermion_sector_view::map_index().

auto basis_states = n_fermion_sector_basis_states(hs, N);
auto view = n_fermion_sector_view(st, hs, N);

for(sv_index_type n = 0; n < basis_states.size(); ++n) {
  view.map_index(basis_states[n]) == n; // true for all n
}
template<typename HSType>
std::vector<sv_index_type> n_fermion_multisector_basis_states(HSType const &hs, std::vector<sector_descriptor<HSType>> const &sectors)

Build and return a list of basis state indices forming an \(N\)-fermion multisector within the full Hilbert space hs. The multisector is defined via a list of contributing sectors (list of \((\{S_i\}, N_i)\) pairs). The order of the indices in the list is consistent with the results of n_fermion_multisector_view::map_index().

auto basis_states = n_fermion_multisector_basis_states(hs, sectors);
auto view = n_fermion_multisector_view(st, hs, sectors);

for(sv_index_type n = 0; n < basis_states.size(); ++n) {
  view.map_index(basis_states[n]) == n; // true for all n
}