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 |
|---|---|---|
|
Type of the elements. |
|
|
Return the |
|
|
Add a value of some type |
The compound-assignment from type |
|
Fill |
The zero value is created by |
|
Return an object of the same type and size as |
Creates a new object as |
|
Apply a function-like object |
In a for-loop, calls |
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 Defined in <libcommute/loperator/sparse_state_vector.hpp>
State vector with a sparse storage of elements (quantum amplitudes).
ScalarTypeis 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
preturnstrue.
-
sparse_state_vector() = delete
View of a compressed state vector
Another way to save memory, in particular in the situation when the
Hilbert space is sparse, is by using
compressed_state_view. A simple array-like container
(e.g. std::vector) compatible with a sparse Hilbert space must have the
size hilbert_space::vec_size(), which exceeds the actual dimension of
the space,
hilbert_space::dim().
The extra required memory is the price to pay for improved performance. This
price, however, can be very steep for the bigger and sparser (i.e. including
many elementary spaces of non-power-of-two dimensions) Hilbert spaces. For
example, a vector storing a state of a system of \(N=10\) spins
\(S=1\) needs to be \(4^N\) elements long, while only \(3^N\) of its
elements represent amplitudes of the physical basis states. As a result, the
memory overhead in this case is huge, \(1 - (3/4)^N \approx 94\%\).
One can solve this issue by allocating an array of the size \(d =\)
hilbert_space::dim()
(only the physical amplitudes are actually stored) and using a
compressed_state_view object to adapt it.
compressed_state_view models the StateVector concept and maps
indices of the physical basis states onto a continuous integer range
\([0; d-1]\) (this operation consumes a bit of extra time). The mapped
indices are then used to address elements of the underlying array.
-
template<typename StateVector, bool Ref = true>
class compressed_state_view Defined in <libcommute/loperator/compressed_state_view.hpp>
View of a
StateVectorobject that translates indices of basis states from a (possibly) sparse Hilbert space onto a continuous range. It is, therefore, sufficient to store only thehilbert_space::dim()physical amplitudes in the underlying state vector object, which can be much more memory-efficient than storing allhilbert_space::vec_size()elements.StateVector- type of the underlying state vector object. Defining a read-only view (such that prohibits update_add_element() operations) requires using aconst-qualified type here. For example, one can useStateVector = std::vector<double>for a read-write view, andStateVector = const std::vector<double>for a read-only view.Ref- by default,compressed_state_viewstores a reference to the underlying state vector. Setting this option tofalsewill 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 toEigen::Map.-
template<typename SV, typename HSType>
compressed_state_view(SV &&sv, HSType const &hs) Construct a view of the state vector
sv, defined in the (sparse) Hilbert spacehs.
-
sv_index_type map_index(sv_index_type index) const
Translate a basis state
indexfrom the Hilbert space to the continuous range[0; hs.dim()-1]. If the Hilbert space is sparse, andindexdoes not correspond to a physical basis state, then the result is undefined.
-
template<typename SV, typename HSType>
<libcommute/loperator/compressed_state_view.hpp> defines two supplemental
factory functions for the compressed_state_view objects.
-
template<typename StateVector, typename HSType>
auto make_comp_state_view(StateVector &&sv, HSType const &hs) -
template<typename StateVector, typename HSType>
auto make_const_comp_state_view(StateVector &&sv, HSType const &hs) Make and return a read/write or constant view of a compressed state vector
svfrom the Hilbert spacehs.
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 Defined in <libcommute/loperator/mapped_basis_view.hpp>
View of a
StateVectorobject 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 aconst-qualified type here. For example, one can useStateVector = std::vector<double>for a read-write view, andStateVector = const std::vector<double>for a read-only view.Ref- by default,mapped_basis_viewstores a reference to the underlying state vector. Setting this option tofalsewill 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 toEigen::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
Defined in <libcommute/loperator/mapped_basis_view.hpp>
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_indicesto 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
Oacts in the Hilbert spacehs. \(|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_listact in the Hilbert spacehs. \(|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_viewfactory 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 byupdate_add_element(). Ifsvis not an lvalue reference, the resulting view will hold a copy ofsv.Warning
To reduce memory footprint,
mapped_basis_viewobjects store a reference to the basis index map owned by their parentbasis_mapperobject. 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.
-
basis_mapper(std::vector<sv_index_type> const &basis_state_indices)
N-fermion sector views
There are two more specialised 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 with
fermions. If a model involves \(M\) fermionic degrees of freedom, then
storing a basis state index map for mapped_basis_view requires
exponentially much memory, \(O(2^M)\).
It is possible to alleviate the memory consumption problem by employing a
(somewhat slower) algorithm that ranks bit patterns in the binary representation
of a basis state index. A computed rank is then used to index into the
\(N\)-fermion (multi)sector.
Warning
n_fermion_sector_view and n_fermion_multisector_view are
incompatible with sparse Hilbert spaces.
-
template<typename StateVector, bool Ref = true, typename RankingAlgorithm = combination_ranking>
class n_fermion_sector_view Defined in <libcommute/loperator/n_fermion_sector_view.hpp>
View of a
StateVectorobject 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.StateVector- type of the underlying state vector object. Defining a read-only view (such that prohibits update_add_element() operations) requires using aconst-qualified type here. For example, one can useStateVector = std::vector<double>for a read-write view, andStateVector = const std::vector<double>for a read-only view.Ref- by default,n_fermion_sector_viewstores a reference to the underlying state vector. Setting this option tofalsewill 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 toEigen::Map.RankingAlgorithm- one of the types implementing bit pattern ranking.-
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 theN-fermion sector of the full Hilbert spacehs.
-
sv_index_type map_index(sv_index_type index) const
Translate a basis state
indexfrom the full Hilbert space to the sector.
-
template<typename SV, typename HSType>
-
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.
-
std::set<typename HSType::index_types> indices
-
template<typename StateVector, bool Ref = true, typename RankingAlgorithm = combination_ranking>
class n_fermion_multisector_view Defined in <libcommute/loperator/n_fermion_sector_view.hpp>
View of a
StateVectorobject 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.It is advised to use
n_fermion_sector_viewinstead, 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 aconst-qualified type here. For example, one can useStateVector = std::vector<double>for a read-write view, andStateVector = const std::vector<double>for a read-only view.Ref- by default,n_fermion_multisector_viewstores a reference to the underlying state vector. Setting this option tofalsewill 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 toEigen::Map.RankingAlgorithm- one of the types implementing bit pattern ranking.-
template<typename SV, typename HSType>
n_fermion_multisector_view(SV &&sv, HSType const &hs, std::vector<sector_descriptor<HSType>> const §ors) Construct a view of the state vector
sv, defined in the \(N\)-fermion multisector of the full Hilbert spacehs. The multisector is defined via a list of contributingsectors(list of \((\{S_i\}, N_i)\) pairs).
-
sv_index_type map_index(sv_index_type index) const
Translate a basis state
indexfrom the full Hilbert space to the multisector.
-
template<typename SV, typename HSType>
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 ofsvwithin the full Hilbert spacehs. Ifsvis not an lvalue reference, the resulting view will hold a copy ofsv. A returned view usescombination_rankingas its bit pattern ranking algorithm.
-
template<typename StateVector, typename HSType>
auto make_nfms_view(StateVector &&sv, HSType const &hs, std::vector<sector_descriptor<HSType>> const §ors) -
template<typename StateVector, typename HSType>
auto make_const_nfms_view(StateVector &&sv, HSType const &hs, std::vector<sector_descriptor<HSType>> const §ors) Make and return a read/write or constant \(N\)-fermion multisector view of
svwithin the full Hilbert spacehs. The multisector is defined via a list of contributingsectors(list of \((\{S_i\}, N_i)\) pairs). Ifsvis not an lvalue reference, the resulting view will hold a copy ofsv. A returned view usescombination_rankingas its bit pattern ranking algorithm.
-
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 spacehs.
-
template<typename HSType>
sv_index_type n_fermion_multisector_size(HSType const &hs, std::vector<sector_descriptor<HSType>> const §ors) Size of the \(N\)-fermion multisector within the full Hilbert space
hs. The multisector is defined via a list of contributingsectors(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 spacehs. The order of the indices in the list is consistent with the results ofn_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 §ors) 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 contributingsectors(list of \((\{S_i\}, N_i)\) pairs). The order of the indices in the list is consistent with the results ofn_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 }
The following classes implement the three ranking algorithms described in [WH22]. They precompute and store a certain amount of data in order to speed up calculations.
-
class combination_ranking
Defined in <libcommute/loperator/n_fermion_sector_view.hpp>
The ranking algorithm based on the combinatorial number system. The \(N\)-fermion (multi)sector view types use this algorithm by default. The storage space required by this class scales as \(O(M \min(N, M - N))\), where \(M\) is the total number of the fermionic degrees of freedom.
-
class combination_ranking
-
template<unsigned int R>
class staggered_ranking Defined in <libcommute/loperator/n_fermion_sector_view.hpp>
The improved combinatorial ranking with staggered lookup and a chunk size of
Rbits. The storage space required by this class scales as \(O\left(2^R (M-R+2)(\frac{M}{2R}+1)\right)\), where \(M\) is the total number of the fermionic degrees of freedom.
-
template<unsigned int R>
“Trie-based ranking of quantum many-body states”, M. Wallerberger and K. Held, Phys. Rev. Research 4, 033238 (2022), https://doi.org/10.1103/PhysRevResearch.4.033238