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 double 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, simplified_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.

template<typename T>
struct linear_function

Defined in <libcommute/utility.hpp>

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

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

Constant term \(c\).

std::vector<std::pair<T, double>> 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(double const_term)

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

linear_function(double 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(double const_term, std::vector<std::pair<T, double>> 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(double 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.

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") << std::endl;
}

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.

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") << std::endl;
}

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

enum spin_component : int

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() << std::endl;
  std::cout << "2J+1 = " << s.multiplicity() << std::endl;
  switch(s.component()) {
    case libcommute::plus:
      std::cout << "+" << std::endl;
      break;
    case libcommute::minus:
      std::cout << "-" << std::endl;
      break;
    case libcommute::z:
      std::cout << "z" << std::endl;
      break;
  }
}