Algebra generators

Algebra generators, such as creation/annihilation operators \(c^\dagger\)/ \(c\) in fermionic and bosonic algebras, are atomic structural units of any expression. Within libcommute’s framework, algebra generators are classes derived from the abstract base libcommute::generator. Every generator carries an index sequence (possibly of zero length), and the respective index types must be passed as template parameters to libcommute::generator. The index types must be less-comparable and form strictly ordered sets so that tuples of the indices (index sequences) are also less-comparable and form a strictly ordered set.

The few methods that derived classes have to override serve a multitude of purposes.

  • Assign a numerical ID shared by all generators of a particular algebra (fermions, bosons, spin operators or a user-defined algebra) and unique to that algebra.

  • Establish a canonical ordering of generators belonging to the same algebra.

  • Describe commutation/anti-commutation rules that can be used to simplify a product of two generators and to put them into the canonical order.

  • Describe further simplification rules applicable to products of 3 and more generators (currently, only one simplification of this kind is implemented).

Generators of different algebras always commute. In a canonically ordered product, generators are placed in the non-decreasing order of their algebra IDs.

The following table summarizes information about predefined generators.

Algebra

Generator type

Algebra ID

Fermions \(c^\dagger_i\)/\(c_i\)

libcommute::generator_fermion

libcommute::fermion

Bosons \(a^\dagger_i\)/\(a_i\)

libcommute::generator_boson

libcommute::boson

Spins \(S_\pm\)/\(S_z\)

libcommute::generator_spin

libcommute::spin

User-defined algebra

A class derived from libcommute::generator

>= libcommute::min_user_defined_algebra_id

Integer constants fermion, boson, spin and min_user_defined_algebra_id mentioned in the 3rd column are defined in <libcommute/algebra_ids.hpp>.

static constexpr int fermion = -3
static constexpr int boson = -2
static constexpr int spin = -1
static constexpr int min_user_defined_algebra_id = 0

generator: abstract base class for algebra generators

template<typename ...IndexTypes>
class generator

Defined in <libcommute/expression/generator.hpp>

The abstract base class for algebra generator types.

IndexTypes - types of indices carried by this generator.

Member type aliases

using index_types = std::tuple<IndexTypes...>

Index tuple type.

using linear_function_t = linear_function<std::unique_ptr<generator>>

Linear combination of generators. This type is used by various methods dealing with transformations of generator products.

Constructor

template<typename ...Args>
generator(Args&&... indices)

Construct generator with given indices.

Copy/move-constructors, assignments and destructor

generator(generator const&) = default
generator(generator&&) noexcept = default
generator &operator=(generator const&) = default
generator &operator=(generator&&) noexcept = default
virtual ~generator()
virtual std::unique_ptr<generator> clone() const = 0

Virtual copy-constructor. Makes a copy of this generator managed by a unique pointer.

Algebra ID

virtual int algebra_id() const = 0

Get the ID of the algebra this generator belongs to.

Index sequence

index_types const &indices() const

Read-only access to the index tuple carried by the generator.

Canonical ordering

protected virtual bool equal(generator const &g) const
protected virtual bool less(generator const &g) const
protected virtual bool greater(generator const &g) const

These methods can be overridden by the derived classes to establish the canonical order of g w.r.t. *this assuming both generators belong to the same algebra. The default implementation compares index tuples of *this and g.

friend bool operator==(generator const &g1, generator const &g2)
friend bool operator!=(generator const &g1, generator const &g2)
friend bool operator<(generator const &g1, generator const &g2)
friend bool operator>(generator const &g1, generator const &g2)

Comparison operators for a pair of generators. First, they compare algebra IDs of g1 and g2. If those are equal, g1.equal(g2), g1.less(g2) or g1.greater(g2) is called.

Product simplification/transformation

virtual var_number swap_with(generator const &g2, linear_function_t &f) const = 0

Given a pair of generators \(g_1\) (*this) and \(g_2\) such that \(g_1 > g_2\), swap_with() must signal what transformation \(g_1 g_2 \mapsto c g_2 g_1 + f(g)\) should be applied to the product \(g_1 g_2\) in order to put it into the canonical order. swap_with() returns the constant \(c\) and writes the linear function of generators \(f(g)\) into its second argument. \(c\) is allowed to be zero.

virtual bool simplify_prod(generator const &g2, linear_function_t &f) const

Given a pair of generators \(g_1\) (*this) and \(g_2\) such that \(g_1 g_2\) is in the canonical order (\(g_1 \leq g_2\)), optionally apply a simplifying transformation \(g_1 g_2 \mapsto f(g)\). If a simplification is actually possible, simplify_prod() must return true and write the linear function \(f(g)\) into its second argument. Otherwise return false.

The default implementation always returns false.

virtual bool reduce_power(int power, linear_function_t &f) const

Given a generator \(g_1\) (*this) and a power \(p > 2\) (power), optionally apply a simplifying transformation \(g_1^p \mapsto f(g)\). If a simplification is actually possible, reduce_power() must return true and write the linear function \(f(g)\) into its second argument. Otherwise return false.

The default implementation always returns false.

Note

Simplifications for \(p = 2\) must be carried out by simplify_prod().

Other methods

virtual void conj(linear_function_t &f)

Return the Hermitian conjugate of generator as a linear function of other generators (write the result into f). The default implementation returns the generator itself.

friend std::ostream &operator<<(std::ostream &os, generator const &g)

Output stream insertion operator. Calls g.print(os).

protected virtual std::ostream &print(std::ostream &os) const

Virtual stream output function to be overridden by the derived classes.

struct var_number

Defined in <libcommute/utility.hpp>

A variadic type that holds an integer, a rational number or a general real floating point number. The actual type of the incapsulated value is determined at construction time.

var_number(int i)

Construct an object holding an integer value i.

var_number(int num, int denom)

Construct an object holding a rational value num / denom.

var_number(double x)

Construct an object holding a real floating point value x.

enum [anonymous]
enumerator integer
enumerator rational
enumerator real
[anonymous] number_type

Type of the stored value.

friend bool operator==(var_number const &vn1, var_number const &vn2)
friend bool operator!=(var_number const &vn1, var_number const &vn2)

Compare the values stored in two var_number objects. Two objects storing values of different types are considered unequal even if the values coincide numerically.

bool is_zero() const

Check whether the stored value is exactly zero.

explicit operator int() const

Retrieve the stored value assuming that it is an integer.

explicit operator double() const

Convert the stored value to a floating point number.

int numerator() const

Assume that a rational value is stored and retrieve its numerator.

int denominator() const

Assume that a rational value is stored and retrieve its denominator.

friend std::ostream &operator<<(std::ostream &os, var_number const &vn)

Output stream insertion operator.

template<typename T>
struct linear_function

Defined in <libcommute/utility.hpp>

A linear function of objects of type T with var_number coefficients,

\[f(x_1, \ldots, x_n) = c + c_1 x_1 + \ldots + c_n x_n.\]
using basis_type = T
var_number const_term = 0;

Constant term \(c\).

std::vector<std::pair<T, var_number>> terms

List of pairs \((x_1, c_1), \ldots, (x_n, c_n)\).

linear_function() = default

Construct an identically vanishing function \(f(x_1, \ldots, x_n) = 0\).

linear_function(var_number const &const_term)

Construct a constant function \(f(x_1, \ldots, x_n) = c\).

linear_function(var_number const &const_term, Args&&... args)

Construct a linear function from a sequence of arguments \(c, x_1, c_1, x_2, c_2, \ldots, x_n, c_n\).

linear_function(var_number const &const_term, std::vector<std::pair<T, var_number>> terms)

Construct a linear function from a constant term and a list of pairs \((x_1, c_1), \ldots, (x_n, c_n)\).

void set(var_number const &const_term, Args&&... args)

Clear all terms and replace them with a sequence of arguments \(c, x_1, c_1, x_2, c_2, \ldots, x_n, c_n\).

bool vanishing() const

Is this linear function identically zero?

Fermions

Fermionic algebra is generated by creation and annihilation operators \(c_i^\dagger\)/\(c_i\) with canonical anti-commutation relations

\[\begin{split}\{c_i, c^\dagger_j\} &= \delta_{ij}, \\ \{c_i, c_j\} &= 0, \\ \{c^\dagger_i, c^\dagger_j\} &= 0.\end{split}\]

The canonical order is defined according to

\[c^\dagger_{i_1} < c^\dagger_{i_2} < c^\dagger_{i_3} < c_{i_3} < c_{i_2} < c_{i_1},\]

where index sequences \(i_k\) satisfy \(i_1 < i_2 < i_3\). In other words,

  • Creation operators precede annihilation operators;

  • Creation operator with the smallest index sequence comes first;

  • Annihilation operator with the smallest index sequence comes last.

Generators of the fermionic algebra are compatible with expressions of any ScalarType, including integer, rational and general real scalars.

template<typename ...IndexTypes>
class generator_fermion : public generator<IndexTypes...>

Defined in <libcommute/expression/generator_fermion.hpp>

Part of the interface not inherited from / identical to libcommute::generator.

bool dagger() const

Returns true for \(c^\dagger\) and false for \(c\).

template<typename ...IndexTypes>
generator_fermion<IndexTypes...> static_indices::make_fermion(bool dagger, IndexTypes&&... indices)

Defined in <libcommute/expression/generator_fermion.hpp>

Make a fermionic creation (dagger = true) or annihilation (dagger = false) operator with given indices.

template<typename ...IndexTypes>
generator_fermion<IndexTypes...> dynamic_indices::make_fermion(bool dagger, IndexTypes&&... indices)

Defined in <libcommute/expression/generator_fermion.hpp>

Make a fermionic creation (dagger = true) or annihilation (dagger = false) operator with a given dynamic index sequence.

template<typename ...IndexTypes>
bool is_fermion(generator<IndexTypes...> const &gen)

Defined in <libcommute/expression/generator_fermion.hpp>

Detect if gen points to a generator of the fermionic algebra.

using namespace libcommute::static_indices;

// Make c^\dagger_{1,up}
auto g = make_fermion(true, 1, "up");

// ...

// If 'g' is a fermionic generator, print whether it is a creation
// or annihilation operator.
if(is_fermion(g)) {
  auto const& f = dynamic_cast<generator_fermion<int, std::string> const&>(g);
  std::cout << (f.dagger() ? "creation" : "annihilation") << '\n';
}

Bosons

Bosonic algebra is generated by creation and annihilation operators \(a_i^\dagger\)/\(a_i\) with canonical commutation relations

\[\begin{split}[a_i, a^\dagger_j] &= \delta_{ij}, \\ [a_i, a_j] &= 0, \\ [a^\dagger_i, a^\dagger_j] &= 0.\end{split}\]

The canonical order is defined according to

\[a^\dagger_{i_1} < a^\dagger_{i_2} < a^\dagger_{i_3} < a_{i_3} < a_{i_2} < a_{i_1},\]

where index sequences \(i_k\) satisfy \(i_1 < i_2 < i_3\). In other words,

  • Creation operators precede annihilation operators;

  • Creation operator with the smallest index sequence comes first;

  • Annihilation operator with the smallest index sequence comes last.

Generators of the bosonic algebra are compatible with expressions of any ScalarType, including integer, rational and general real scalars.

template<typename ...IndexTypes>
class generator_boson : public generator<IndexTypes...>

Defined in <libcommute/expression/generator_boson.hpp>

Part of the interface not inherited from / identical to libcommute::generator.

bool dagger() const

Returns true for \(a^\dagger\) and false for \(a\).

template<typename ...IndexTypes>
generator_boson<IndexTypes...> static_indices::make_boson(bool dagger, IndexTypes&&... indices)

Defined in <libcommute/expression/generator_boson.hpp>

Make a bosonic creation (dagger = true) or annihilation (dagger = false) operator with given indices.

template<typename ...IndexTypes>
generator_fermion<IndexTypes...> dynamic_indices::make_boson(bool dagger, IndexTypes&&... indices)

Defined in <libcommute/expression/generator_boson.hpp>

Make a bosonic creation (dagger = true) or annihilation (dagger = false) operator with a given dynamic index sequence.

template<typename ...IndexTypes>
bool is_boson(generator<IndexTypes...> const &gen)

Defined in <libcommute/expression/generator_boson.hpp>

Detect if gen points to a generator of the bosonic algebra.

using namespace libcommute::static_indices;

// Make a^\dagger_1
auto g = make_boson(true, 1);

// ...

// If 'g' is a bosonic generator, print whether it is a creation or
// annihilation operator.
if(is_boson(g)) {
  auto const& b = dynamic_cast<generator_boson<int> const&>(g);
  std::cout << (b.dagger() ? "creation" : "annihilation") << '\n';
}

Spins

libcommute supports algebra of spin operators for \(S = 1/2\) as well as for higher integer and half-integer spins. Generators of spin algebras with different \(S\) share the same algebra ID and are distinguished by an extra integer data member multiplicity equal to \(2S+1\). For a fixed \(S\) and a set of indices, the spin algebra is generated by the triplet of operators \(S_+\), \(S_-\) and \(S_z\) subject to the following commutation relations.

\[\begin{split}[S_+, S_-] &= 2 S_z, \\ [S_z, S_+] &= S_+, \\ [S_z, S_-] &= -S_-.\end{split}\]

Note

Using \(S_\pm\) instead of \(S_x\), \(S_y\) as algebra generators is beneficial because all coefficients in the commutation relations above are real. \(S_x\)/\(S_y\) would necessitate the complex scalar types in all libcommute::expression objects.

The canonical order is defined according to

\[\begin{split}S_{1,+}^{S=1/2} < S_{1,-}^{S=1/2} < S_{1,z}^{S=1/2} < S_{2,+}^{S=1/2} < S_{2,-}^{S=1/2} < S_{2,z}^{S=1/2} < \\ < S_{2,+}^{S=3/2} < S_{2,-}^{S=3/2} < S_{2,z}^{S=3/2} < S_{2,+}^{S=3/2} < S_{2,-}^{S=3/2} < S_{2,z}^{S=3/2}.\end{split}\]

In other words,

  • Operators with lower \(S\) precede operators with higher \(S\).

  • Among operators with the same \(S\), the operator with the smallest index sequence comes first.

  • Among operators with the same \(S\) and index sequence, \(S_+\) comes first followed by \(S_-\) and eventually by \(S_z\).

Generators of the spin algebra are incompatible with expressions of an integer ScalarType, and require at least rational scalars.

enum spin_component : std::uint8_t

Component of spin operator.

enumerator plus = 0

\(S_+\).

enumerator minus = 1

\(S_-\).

enumerator z = 2

\(S_z\).

template<typename ...IndexTypes>
class generator_spin : public generator<IndexTypes...>

Defined in <libcommute/expression/generator_spin.hpp>

Part of the interface not inherited from / identical to libcommute::generator.

template<typename ...Args>
generator_spin(spin_component c, Args&&... indices)

Construct generator \(S_+\), \(S_-\) or \(S_z\) for spin \(S=1/2\) with given indices.

template<typename ...Args>
generator_spin(double spin, spin_component c, Args&&... indices)

Construct generator \(S_+\), \(S_-\) or \(S_z\) for spin spin with given indices.

double spin() const

Read-only access to generator’s spin \(S\).

int multiplicity() const

Read-only access to generator’s multiplicity \(2S+1\).

libcommute::spin_component component() const

Is this generator \(S_+\), \(S_-\) or \(S_z\)?

template<typename ...IndexTypes>
generator_spin<IndexTypes...> static_indices::make_spin(spin_component c, IndexTypes&&... indices)

Defined in <libcommute/expression/generator_spin.hpp>

Make generator \(S_+\), \(S_-\) or \(S_z\) for spin \(S=1/2\) with given indices.

template<typename ...IndexTypes>
generator_spin<IndexTypes...> static_indices::make_spin(double spin, spin_component c, IndexTypes&&... indices)

Defined in <libcommute/expression/generator_spin.hpp>

Make generator \(S_+\), \(S_-\) or \(S_z\) for spin spin with given indices.

template<typename ...IndexTypes>
generator_spin<dyn_indices> dynamic_indices::make_spin(spin_component c, IndexTypes&&... indices)

Defined in <libcommute/expression/generator_spin.hpp>

Make generator \(S_+\), \(S_-\) or \(S_z\) for spin \(S=1/2\) with a given dynamic index sequence.

template<typename ...IndexTypes>
generator_spin<dyn_indices> libcommute::dynamic_indices::make_spin(double spin, libcommute::spin_component c, IndexTypes&&... indices)

Defined in <libcommute/expression/generator_spin.hpp>

Make generator \(S_+\), \(S_-\) or \(S_z\) for spin spin with a given dynamic index sequence.

template<typename ...IndexTypes>
bool libcommute::is_spin(libcommute::generator<IndexTypes...> const &gen)

Defined in <libcommute/expression/generator_spin.hpp>

Detect if gen points to a generator of the spin algebra.

using namespace libcommute::static_indices;

// Make S^{J=1}_{1,+}
auto g = make_spin(1.0, libcommute::plus, 1);

// ...

// If 'g' is a spin algebra generator, print its properties.
if(is_spin(g)) {
  auto const& s = dynamic_cast<generator_spin<int> const&>(g);

  std::cout << "J = " << s.spin() << '\n';
  std::cout << "2J+1 = " << s.multiplicity() << '\n';
  switch(s.component()) {
    case libcommute::plus:
      std::cout << "+\n";
      break;
    case libcommute::minus:
      std::cout << "-\n";
      break;
    case libcommute::z:
      std::cout << "z\n";
      break;
  }
}