# Portions Copyright 2021 Huawei Technologies Co., Ltd
# Portions Copyright 2017 The OpenFermion Developers.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================
"""This module is generated the Fermion Operator"""
from mindquantum.parameterresolver import ParameterResolver as PR
from ._base_operator import _Operator
def _check_valid_fermion_operator_term(term):
"""Check valid fermion operator term."""
if term is not None and term != '':
if not isinstance(term, (str, tuple)):
raise ValueError(
'Fermion operator requires a string or a tuple, but get {}'.
format(type(term)))
if isinstance(term, str):
terms = term.split(' ')
for t in terms:
if (t.endswith('^')
and not t[:-1].isdigit()) or (not t.endswith('^')
and not t.isdigit()):
if t:
raise ValueError(
'Invalid fermion operator term {}'.format(t))
if isinstance(term, tuple):
for t in term:
if len(t) != 2 or not isinstance(t[0], int) or not isinstance(
t[1], int) or t[0] < 0 or t[1] not in [0, 1]:
raise ValueError(
'Invalid fermion operator term {}'.format(t))
[docs]class FermionOperator(_Operator):
r"""
The Fermion Operator such as FermionOperator(' 4^ 3 9 3^ ')
are used to represent :math:`a_4^\dagger a_3 a_9 a_3^\dagger`.
These are the Basic Operators to describe a fermionic system,
such as a Molecular system.
The FermionOperator are follows the anti-commutation relationship.
Args:
terms (str): The input term of fermion operator. Default: None.
coefficient (Union[numbers.Number, str, ParameterResolver]): The
coefficient for the corresponding single operators Default: 1.0.
Examples:
>>> from mindquantum.ops import FermionOperator
>>> a_p_dagger = FermionOperator('1^')
>>> a_p_dagger
1.0 [1^]
>>> a_q = FermionOperator('0')
>>> a_q
1.0 [0]
>>> zero= FermionOperator()
>>> 0
>>> identity= FermionOperator('')
>>> 1.0 []
>>> # check with coefficient
>>> para_op = FermionOperator('0 1^', 'x')
x [0 1^]
>>> para_dt = {'x':2}
>>> op = para_op.subs(para_dt)
>>> op
2 [0 1^]
"""
__hash__ = None
def __init__(self, term=None, coefficient=1.0):
super(FermionOperator, self).__init__(term, coefficient)
_check_valid_fermion_operator_term(term)
self.operators = {1: '^', 0: '', '^': '^', '': ''}
self.gates_number = 0
self.qubit_type = False
if term is not None:
if term == '':
term = self._parse_term(())
else:
term = self._parse_term(term)
self.terms[term] = self.coefficient
def _simplify(self, terms, coefficient=1.0):
"""Simplify a term."""
return coefficient, tuple(terms)
def _parse_string(self, terms_string):
"""
Parse a term given as a string type
e.g. For FermionOperator:
4^ 3 -> ((4, 1),(3, 0))
Note here the '1' and '0' in the second col represents creation
and annihilaiton operator respectively
Returns:
tuple, return a tuple list, such as ((4, 1),(3, 0))
Raises:
'1.5 4^ 3' is not the proper format and
could raise TypeError.
"""
map_operator_to_integer_rep = lambda operator: 1 if operator == '^' else 0
terms = terms_string.split()
terms_to_tuple = []
for sub_term in terms:
index = int(sub_term[0])
operator = sub_term[1:]
# Handle such cases: 10^, 100^, ...
if len(sub_term) >= 2:
if '^' in sub_term:
operator = '^'
index = int(sub_term[:sub_term.index(operator)])
else:
operator = ''
index = int(sub_term)
if operator not in self.operators:
raise ValueError(
'Invalid type of operator {}.'
'The Fermion operator should be one of this {}'.format(
operator, self.operators))
if index < 0:
raise ValueError("Invalid index {}.The qubit index should be\
non negative integer".format(self.operators))
terms_to_tuple.append(
(index, map_operator_to_integer_rep(operator)))
# check the commutate terms with same index in the list and
# replace it with the corresponding commutation relationship
return tuple(terms_to_tuple)
def __str__(self):
"""
Return an easy-to-read string representation of the FermionOperator.
"""
if not self.terms:
return '0'
string_rep = ''
term_cnt = 0
for term, coeff in sorted(self.terms.items()):
term_cnt += 1
if isinstance(coeff, PR):
tmp_string = '{} ['.format(
coeff.expression()) # begin of the '['
else:
tmp_string = '{} ['.format(coeff) # begin of the '['
# deal with this situation (1,'X') or [1, 'X']
if term == ():
if self.size == 1:
tmp_string.join(' ]')
else:
pass
elif isinstance(term[0], int):
index, operator = term
if operator in self.operators:
tmp_string += '{}{} '.format(index,
self.operators[operator])
else:
for sub_term in term:
index, operator = sub_term
# check validity, if checked before,
# then we can take away this step
if operator in self.operators:
tmp_string += '{}{} '.format(index,
self.operators[operator])
if term_cnt < len(self.terms):
string_rep += '{}] +\n'.format(
tmp_string.strip()) # end of the ']'
else:
string_rep += '{}] '.format(
tmp_string.strip()) # end of the ']'
return string_rep
def __repr__(self):
return str(self)
@property
def imag(self):
"""
Convert the coeff to its imag part.
Returns:
FermionOperator, the imag part of this fermion operator.
Examples:
>>> from mindquantum.ops import FermionOperator
>>> f = FermionOperator('0', 1 + 2j) + FermionOperator('0^', 'a')
>>> f.imag.compress()
2.0 [0]
"""
out = FermionOperator()
for k, v in self.terms.items():
out.terms[k] = v.imag
return out
@property
def real(self):
"""
Convert the coeff to its real part.
Returns:
FermionOperator, the real part of this fermion operator.
Examples:
>>> from mindquantum.ops import FermionOperator
>>> f = FermionOperator('0', 1 + 2j) + FermionOperator('0^', 'a')
>>> f.real.compress()
1.0 [0] +
a [0^]
"""
out = FermionOperator()
for k, v in self.terms.items():
out.terms[k] = v.real
return out
[docs] def normal_ordered(self):
"""Return the normal ordered form of the Fermion Operator.
Returns:
FermionOperator, the normal ordered FermionOperator.
Exmples:
>>> from mindquantum.ops import FermionOperator
>>> origin = FermionOperator('0 1^')
>>> origin
1.0 [0 1^]
>>> origin.normal_ordered()
-1.0 [1^ 0]
"""
ordered_op = self.__class__()
for term, coeff in self.terms.items():
ordered_op += _normal_ordered_term(term, coeff)
return ordered_op
def _normal_ordered_term(term, coefficient):
r"""Return the normal ordered term of the FermionOperator with high index
and creation operator in front.
eg. :math:`a_3\dagger a_2\dagger a_1 a_0`
"""
term = list(term)
ordered_term = FermionOperator()
for i in range(1, len(term)):
for j in range(i, 0, -1):
left_sub_term = term[j - 1]
right_sub_term = term[j]
# Swap operators if left operator is a and right operator is
# a^\dagger
if not left_sub_term[1] and right_sub_term[1]:
term[j], term[j - 1] = left_sub_term, right_sub_term
coefficient = -1 * coefficient
# If indice are same, employ the anti-commutation relationship
# And generate the new term
if left_sub_term[0] == right_sub_term[0]:
new_term = term[:(j - 1)] + term[(j + 1):]
ordered_term += _normal_ordered_term(
new_term, -1 * coefficient)
elif left_sub_term[1] == right_sub_term[1]:
# If indice are same,evaluate it to zero.
if left_sub_term[0] == right_sub_term[0]:
return ordered_term
# Swap them if same operator but lower index on left
if left_sub_term[0] < right_sub_term[0]:
term[j], term[j - 1] = left_sub_term, right_sub_term
coefficient = -1 * coefficient
# Add the term and return.
ordered_term += FermionOperator(_fermion_tuple_to_string(tuple(term)),
coefficient)
return ordered_term
def _fermion_tuple_to_string(term):
s = []
for i in term:
if i[1] == 1:
s.append('{}^'.format(i[0]))
else:
s.append(str(i[0]))
return ' '.join(s)