MajoranaOperator¶
- class MajoranaOperator¶
Bases:
objectA Majorana fermion operator.
Definition¶
This operator is defined by a linear combination of products of Majorana operators [1], which can be defined in terms of the standard fermionic second-quantization creation and annihilation operators (see also
FermionOperator):\[\gamma = a^\dagger + a ~~\text{and}~~ \gamma' = i(a^\dagger - a)\]The key property that a Majorana fermion is its own antiparticle becomes immediately apparent:
\[\gamma_i = \gamma_i^\dagger ~~\text{and}~~ \gamma_i^2 = (\gamma_i^\dagger)^2 = 1\]This result in the following anti-commutation relations for \(2n\) Majorana fermions:
\[\left\{\gamma_i,\gamma_j\right\} = 2\delta_{ij}\]This makes the definition of the entire operator the following:
\[\text{\texttt{MajoranaOperator}} = \sum_i c_i \bigotimes_j \hat{\gamma_j} \, ,\]where \(c_i\) is the (complex) coefficient making up the linear combination of products of \(\gamma_j\). The index \(j\) can take any value between 0 and the number of majorana fermionic modes acted upon by the operator minus 1.
Implementation¶
This class stores the terms and coefficients in multiple sparse vectors, akin to the compressed sparse row format commonly used for sparse matrices. More concretely, a single operator contains 4 arrays:
coeffsA vector of complex coefficients consisting of two 64-bit floating point numbers.
modesA vector of 32-bit integers storing the majorana mode indices acted upon.
boundariesA vector of integers indicating the boundaries in
actionsandindices.The integers in
modesindex the Majorana modes, \(j\). When using the convenience functiongamma(), even (odd) indices are used for :math`gamma` (\(\gamma'\)).This data structure allows for very efficient construction and manipulation of operators. However, it implies that duplicate terms may be contained in an operator at any moment. These must be resolved manually through the use of
simplify().Construction¶
An operator can be constructed directly by providing the arrays outlined above:
>>> from qiskit_fermions.operators import MajoranaOperator >>> coeffs = [1.0, -2.0, 3.0j, -0.5j] >>> modes = [0, 1, 0, 2, 0, 1, 2, 3] >>> boundaries = [0, 0, 2, 4, 8] >>> op = MajoranaOperator(coeffs, modes, boundaries) >>> print(op) 1.000000e0 +0.000000e0j * () -2.000000e0 +0.000000e0j * (0 1) -0.000000e0-5.000000e-1j * (0 1 2 3) 0.000000e0 +3.000000e0j * (0 2)
For convenience, it is possible to construct an operator from a Python dictionary like so:
>>> from qiskit_fermions.operators import gamma >>> op = MajoranaOperator.from_dict( ... { ... (): 1.0, ... (gamma(0, False), gamma(0, True)): -2.0, ... (gamma(0, False), gamma(1, False)): 3.0j, ... (gamma(0, False), gamma(0, True), gamma(1, False), gamma(1, True)): -0.5j, ... } ... ) >>> print(op) 1.000000e0 +0.000000e0j * () -2.000000e0 +0.000000e0j * (0 1) -0.000000e0-5.000000e-1j * (0 1 2 3) 0.000000e0 +3.000000e0j * (0 2)
In this example, we have leveraged
gamma()for creating the Majorana operators.In addition, the following construction and quick helper methods are available:
Iteration¶
Since the underlying data structure is implemented in Rust and has a non-trivial layout, it cannot be iterated over directly:
>>> list(iter(op)) Traceback (most recent call last): ... TypeError: 'qiskit_fermions.operators.majorana_operator.MajoranaOperator' object is not iterable
Instead, this class provides custom iterators to fulfill this purpose:
>>> list(sorted(op.iter_terms())) [([], (1+0j)), ([0, 1], (-2+0j)), ([0, 1, 2, 3], (-0-0.5j)), ([0, 2], 3j)]
See also
iter_terms()For more relevant implementation details.
The table below lists all available iterators:
An iterator over the operator's terms.
Arithmetics¶
The following arithmetic operations are supported:
Addition/Subtraction¶
>>> op = MajoranaOperator.one() >>> (op + op).simplify() MajoranaOperator.from_dict({(): 2+0j}) >>> (op - op).simplify() MajoranaOperator.from_dict({}) >>> op += op >>> op.simplify() MajoranaOperator.from_dict({(): 2+0j}) >>> op -= op >>> op.simplify() MajoranaOperator.from_dict({})
Scalar Multiplication/Divison¶
>>> op = MajoranaOperator.one() >>> (2 * op).simplify() MajoranaOperator.from_dict({(): 2+0j}) >>> (op / 2).simplify() MajoranaOperator.from_dict({(): 0.5+0j}) >>> op *= 2 >>> op.simplify() MajoranaOperator.from_dict({(): 2+0j}) >>> op /= 2 >>> op.simplify() MajoranaOperator.from_dict({(): 1+0j})
Operator Composition¶
Note
Operator composition corresponds to left-multiplication:
c = a & bcorresponds to \(C = B A\). In other words, the composition of two operators returns a resulting operator that performs “firstaand thenb”.>>> op1 = MajoranaOperator.from_dict({(): 2.0, (gamma(0, False),): 3.0}) >>> op2 = MajoranaOperator.from_dict({(): 1.5, (gamma(0, True),): 4.0}) >>> comp = (op1 & op2).simplify() >>> print(comp) 3.000000e0 +0.000000e0j * () 4.500000e0 +0.000000e0j * (0) 8.000000e0 +0.000000e0j * (1) 1.200000e1 +0.000000e0j * (1 0) >>> op2 &= op1 >>> print(op2.simplify()) 3.000000e0 +0.000000e0j * () 4.500000e0 +0.000000e0j * (0) 1.200000e1 +0.000000e0j * (0 1) 8.000000e0 +0.000000e0j * (1) >>> squared = (op1 ** 2).simplify() >>> print(squared) 4.000000e0 +0.000000e0j * () 1.200000e1 +0.000000e0j * (0) 9.000000e0 +0.000000e0j * (0 0)
Other Operations¶
In addition to the magic methods that correspond to the arithmetic operations outlined above, the following methods are available:
adjoint()Returns the Hermitian conjugate (or adjoint) of this operator.
ichop([atol])Removes terms whose coefficient magnitude lies below the provided threshold.
simplify([atol])Returns an equivalent but simplified operator.
normal_ordered([reduce])Returns an equivalent operator with normal ordered terms.
Properties¶
Finally, various methods exist to check certain properties of an operator:
is_hermitian([atol])Returns whether this operator is Hermitian.
Returns the many-body order of this operator.
is_even()Returns whether this operator is even.
Methods
- adjoint()¶
Returns the Hermitian conjugate (or adjoint) of this operator.
This affects the terms and coefficients as follows:
the actions in each term reverse their order
the coefficients are complex conjugated
>>> from qiskit_fermions.operators import MajoranaOperator >>> op = MajoranaOperator.from_dict({(): -1.0j, (gamma(0, False), gamma(0, True)): 1.0}) >>> adj = op.adjoint() >>> print(adj) -0.000000e0 +1.000000e0j * () 1.000000e0 -0.000000e0j * (1 0)
- equiv(other, atol=1e-08)¶
Checks this operator for equivalence with another operator.
Equivalence in this context means approximate equality up to the specified absolute tolerance. To be more precise, this method returns
True, when all the absolute values of the coefficients in the differenceother - selfare below the specified thresholdatol.>>> from qiskit_fermions.operators import MajoranaOperator >>> op = MajoranaOperator.from_dict({(): 1e-7}) >>> zero = MajoranaOperator.zero() >>> op.equiv(zero) False >>> op.equiv(zero, 1e-6) True >>> op.equiv(zero, 1e-9) False
- Parameters:
other – the other operator to compare with.
atol – the absolute tolerance for the comparison. This value defaults to
1e-8.
- from_dict()¶
Constructs a new operator from a dictionary.
>>> from qiskit_fermions.operators import MajoranaOperator >>> op = MajoranaOperator.from_dict( ... { ... (): 1.0-1.0j, ... (0, 1): 2.0, ... } ... ) >>> print(op) 1.000000e0 -1.000000e0j * () 2.000000e0 +0.000000e0j * (0 1)
- Parameters:
data – a dictionary mapping tuples of terms to complex coefficients. Each key is a tuple of integers, indexing the Majorana modes. You may use
gamma()to simplify the assignment of even and odd indices to \(\gamma\) and \(\gamma'\).- Returns:
A new operator.
- ichop(atol=1e-08)¶
Removes terms whose coefficient magnitude lies below the provided threshold.
Caution
This method truncates coefficients greedily! If the acted upon operator may contain separate coefficients for duplicate terms consider calling
simplify()instead!>>> from qiskit_fermions.operators import MajoranaOperator >>> op = MajoranaOperator.from_dict({(): 1e-4, (0,): 1e-6, (1,): 1e-10}) >>> print(op) 1.000000e-4 +0.000000e0j * () 1.000000e-6 +0.000000e0j * (0) 1.000000e-10 +0.000000e0j * (1) >>> op.ichop() >>> print(op) 1.000000e-4 +0.000000e0j * () 1.000000e-6 +0.000000e0j * (0) >>> op.ichop(1e-5) >>> print(op) 1.000000e-4 +0.000000e0j * ()
- Parameters:
atol – the absolute tolerance for the cutoff. This value defaults to
1e-8.
- is_even()¶
Returns whether this operator is even.
Note
An operator is considered even when all of its terms contain an even number of actions.
>>> from qiskit_fermions.operators import MajoranaOperator >>> op = MajoranaOperator.from_dict({(0, 1): 1}) >>> op.is_even() True >>> op = MajoranaOperator.from_dict({(0,): 1}) >>> op.is_even() False
- Returns:
Whether this operator is even.
- is_hermitian(atol=1e-08)¶
Returns whether this operator is Hermitian.
Note
This check is implemented using
equiv()on thenormal_ordered()difference ofselfand itsadjoint()andzero().>>> from qiskit_fermions.operators import MajoranaOperator >>> op = MajoranaOperator.from_dict({ ... (0, 1, 2, 3): 1.00001j, ... (3, 2, 1, 0): -1j, ... }) >>> op.is_hermitian() False >>> op.is_hermitian(1e-4) True
- Parameters:
atol – The numerical accuracy upto which coefficients are considered equal. This value defaults to
1e-8.- Returns:
Whether this operator is Hermitian.
- iter_terms()¶
An iterator over the operator’s terms.
Warning
Mutating the iteration items does not affect the underlying operator data.
>>> from qiskit_fermions.operators import MajoranaOperator >>> op = MajoranaOperator.from_dict({(): 2.0, (0,): 1.0, (1,): -1.0j}) >>> list(sorted(op.iter_terms())) [([], (2+0j)), ([0], (1+0j)), ([1], (-0-1j))]
- many_body_order()¶
Returns the many-body order of this operator.
Note
The many-body order is defined as the length of the longest term contained in the operator.
>>> from qiskit_fermions.operators import MajoranaOperator >>> op = MajoranaOperator.from_dict({(0, 1, 2, 3): 1}) >>> op.many_body_order() 4
- Returns:
The many-body order of this operator.
- normal_ordered(reduce=True)¶
Returns an equivalent operator with normal ordered terms.
The normal order of an operator term is defined such that all actions are ordered by lexicographically descending indices. With the convention set forth by
gamma()to place \(\gamma\) (\(\gamma'\)) on even (odd) indices, this results in the following example:[\gamma(1, True), \gamma(1, False), \gamma(0, True), \gamma(0, False)] = [3, 2, 1, 0].>>> from qiskit_fermions.operators import MajoranaOperator >>> op = MajoranaOperator.from_dict({(gamma(0, False), gamma(0, True), gamma(0, False)): 1}) >>> print(op.normal_ordered(reduce=False)) -1.000000e0 +0.000000e0j * (1 0 0) >>> print(op.normal_ordered(reduce=True)) -1.000000e0 +0.000000e0j * (1)
- Parameters:
reduce – whether to reduce each term to its minimal form by removing actions that square to the identity. See also the example above.
- Returns:
An equivalent but normal-ordered operator.
- one()¶
Constructs the multiplicative identity operator.
Composing the operator that is constructed by this method with another one has no effect.
>>> from qiskit_fermions.operators import MajoranaOperator >>> op = MajoranaOperator.from_dict({(): 2.0}) >>> one = MajoranaOperator.one() >>> op & one == op True
- simplify(atol=1e-08)¶
Returns an equivalent but simplified operator.
The simplification process first sums all coefficients that belong to equal terms and then only retains those whose total coefficient exceeds the specified tolerance (just like
ichop()).When an operator has been arithmetically manipulated or constructed in a way that does not guarantee unique terms, this method should be called before applying any method that filters numerically small coefficients to avoid loss of information. See the example below which showcases how
ichop()can truncate terms that sum to a total coefficient magnitude which should not be truncated:>>> from qiskit_fermions.operators import MajoranaOperator >>> coeffs = [1e-5] * int(1e5) >>> boundaries = [0] + [0] * int(1e5) >>> op = MajoranaOperator(coeffs, [], boundaries) >>> canon = op.simplify(1e-4) >>> assert canon.equiv(op.one(), 1e-6) >>> op.ichop(1e-4) >>> assert op.equiv(op.zero(), 1e-6)
- Parameters:
atol – the absolute tolerance for the cutoff. This value defaults to
1e-8.- Returns:
An equivalent but simplified operator.
- zero()¶
Constructs the additive identity operator.
Adding the operator that is constructed by this method to another one has no effect.
>>> from qiskit_fermions.operators import MajoranaOperator >>> op = MajoranaOperator.from_dict({(): 2.0}) >>> zero = MajoranaOperator.zero() >>> op + zero == op True