Source code for mindquantum.core.operators.fermion_operator

# 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.
# ============================================================================

# pylint: disable=duplicate-code

"""This module is generated the Fermion Operator."""

import json
from functools import lru_cache

import numpy as np
from scipy.sparse import csr_matrix, kron

from mindquantum.utils.type_value_check import _check_input_type, _check_int_type

from ..parameterresolver import ParameterResolver
from ._base_operator import _Operator


@lru_cache()
def _n_sz(n):
    if n == 0:
        return csr_matrix(np.array([1]), dtype=np.complex128)
    tmp = [csr_matrix(np.array([[1, 0], [0, -1]], dtype=np.complex128)) for _ in range(n)]
    for i in tmp[1:]:
        tmp[0] = kron(tmp[0], i)
    return tmp[0]


@lru_cache()
def _n_identity(n):
    """N_identity."""
    if n == 0:
        return csr_matrix(np.array([1]), dtype=np.complex128)
    tmp = [csr_matrix(np.array([[1, 0], [0, 1]], dtype=np.complex128)) for _ in range(n)]
    for i in tmp[1:]:
        tmp[0] = kron(tmp[0], i)
    return tmp[0]


@lru_cache()
def _single_fermion_word(idx, dag, n_qubits):
    """Single_fermion_word."""
    matrix = csr_matrix(np.array([[0, 1], [0, 0]], dtype=np.complex128))
    if dag:
        matrix = csr_matrix(np.array([[0, 0], [1, 0]], dtype=np.complex128))
    return kron(_n_identity(n_qubits - 1 - idx), kron(matrix, _n_sz(idx)))


@lru_cache()
def _two_fermion_word(idx1, dag1, idx2, dag2, n_qubits):
    """Two_fermion_word."""
    return _single_fermion_word(idx1, dag1, n_qubits) * _single_fermion_word(idx2, dag2, n_qubits)


def _check_valid_fermion_operator_term(fo_term):
    """Check valid fermion operator term."""
    if fo_term is not None and fo_term != '':
        if not isinstance(fo_term, (str, tuple)):
            raise ValueError(f'Fermion operator requires a string or a tuple, but get {type(fo_term)}')
        if isinstance(fo_term, str):
            terms = fo_term.split(' ')
            for term in terms:
                if (term.endswith('^') and not term[:-1].isdigit()) or (not term.endswith('^') and not term.isdigit()):
                    if term:
                        raise ValueError(f'Invalid fermion operator term {term}')
        if isinstance(fo_term, tuple):
            for term in fo_term:
                if (
                    len(term) != 2
                    or not isinstance(term[0], int)
                    or not isinstance(term[1], int)
                    or term[0] < 0
                    or term[1] not in [0, 1]
                ):
                    raise ValueError(f'Invalid fermion operator term {term}')


[文档]class FermionOperator(_Operator): r""" Definition of a Fermion Operator. 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.core.operators import FermionOperator >>> a_p_dagger = FermionOperator('1^') >>> a_p_dagger 1.0 [1^] >>> a_q = FermionOperator('0') >>> a_q 1.0 [0] >>> zero = FermionOperator() >>> zero 0 >>> identity= FermionOperator('') >>> identity 1.0 [] >>> para_op = FermionOperator('0 1^', 'x') >>> para_op 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): """Initialize a FermionOperator object.""" super().__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. """ def map_operator_to_integer_rep(operator): """Map operator to integer.""" return 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( f'Invalid type of operator {operator}.' f'The Fermion operator should be one of this {self.operators}' ) if index < 0: raise ValueError(f"Invalid index {self.operators}.The qubit index should be non negative integer") 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 to_openfermion(self): """Convert fermion operator to openfermion format.""" # pylint: disable=import-outside-toplevel from openfermion import FermionOperator as OFFermionOperator terms = {} for k, v in self.terms.items(): if not v.is_const(): raise ValueError("Cannot convert parameteized fermion operator to openfermion format") terms[k] = v.const fermion_operator = OFFermionOperator() fermion_operator.terms = terms return fermion_operator
[文档] @staticmethod def from_openfermion(of_ops): """ Convert openfermion fermion operator to mindquantum format. Args: of_ops (openfermion.FermionOperator): fermion operator from openfermion. Returns: FermionOperator, fermion operator from mindquantum. """ # pylint: disable=import-outside-toplevel from openfermion import FermionOperator as OFFermionOperator _check_input_type('of_ops', OFFermionOperator, of_ops) fermion_operator = FermionOperator() for k, v in of_ops.terms.items(): fermion_operator.terms[k] = ParameterResolver(v) return fermion_operator
def __str__(self): # pylint: disable=too-many-branches """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, ParameterResolver): tmp_string = f'{coeff.expression()} [' # begin of the '[' else: tmp_string = f'{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 += f'{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 += f'{index}{self.operators[operator]} ' if term_cnt < len(self.terms): string_rep += f'{tmp_string.strip()}] +\n' # end of the ']' else: string_rep += f'{tmp_string.strip()}] ' # end of the ']' return string_rep def __repr__(self): """Return a string representation of the object.""" return str(self)
[文档] def matrix(self, n_qubits=None): # pylint: disable=too-many-branches """ Convert this fermion operator to csr_matrix under jordan_wigner mapping. Args: n_qubits (int): The total qubit of final matrix. If None, the value will be the maximum local qubit number. Default: None. """ from .utils import ( # pylint: disable=import-outside-toplevel,cyclic-import count_qubits, ) if not self.terms: raise ValueError("Cannot convert empty fermion operator to matrix") n_qubits_local = count_qubits(self) if n_qubits_local == 0 and n_qubits is None: raise ValueError("You should specific n_qubits for converting a identity fermion operator.") if n_qubits is None: n_qubits = n_qubits_local _check_int_type("n_qubits", n_qubits) if n_qubits < n_qubits_local: raise ValueError( f"Given n_qubits {n_qubits} is small than qubit of fermion operator, which is {n_qubits_local}." ) out = 0 for term, coeff in self.terms.items(): if not coeff.is_const(): raise RuntimeError("Cannot convert a parameterized fermion operator to matrix.") coeff = coeff.const if not term: out += csr_matrix(np.identity(2**n_qubits, dtype=np.complex128)) * coeff else: tmp = 1 group = [[]] for idx, dag in term: if len(group[-1]) < 4: group[-1].append(idx) group[-1].append(dag) if len(group[-1]) == 4: group.append([]) for gate in group: if gate: if len(gate) == 4: tmp *= _two_fermion_word(gate[0], gate[1], gate[2], gate[3], n_qubits) else: tmp *= _single_fermion_word(gate[0], gate[1], n_qubits) out += tmp * coeff return out
@property def imag(self): """ Convert the coefficient to its imag part. Returns: FermionOperator, the imag part of this fermion operator. Examples: >>> from mindquantum.core.operators import FermionOperator >>> f = FermionOperator('0', 1 + 2j) + FermionOperator('0^', 'a') >>> f.imag.compress() 2.0 [0] """ out = FermionOperator() for term, coeff in self.terms.items(): out.terms[term] = coeff.imag return out @property def real(self): """ Convert the coefficient to its real part. Returns: FermionOperator, the real part of this fermion operator. Examples: >>> from mindquantum.core.operators import FermionOperator >>> f = FermionOperator('0', 1 + 2j) + FermionOperator('0^', 'a') >>> f.real.compress() 1.0 [0] + a [0^] """ out = FermionOperator() for term, coeff in self.terms.items(): out.terms[term] = coeff.real return out
[文档] def normal_ordered(self): """ Return the normal ordered form of the Fermion Operator. Returns: FermionOperator, the normal ordered FermionOperator. Examples: >>> from mindquantum.core.operators 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 dumps(self, indent=4): r""" Dump FermionOperator into JSON(JavaScript Object Notation). Args: indent (int): Then JSON array elements and object members will be pretty-printed with that indent level. Default: 4. Returns: JSON (str), the JSON strings of FermionOperator Examples: >>> from mindquantum.core.operators import FermionOperator >>> f = FermionOperator('0', 1 + 2j) + FermionOperator('0^', 'a') >>> len(f.dumps()) 443 """ if indent is not None: _check_int_type('indent', indent) dic = {} for term, coeff in self.terms.items(): string = _fermion_tuple_to_string(term) dic[string] = coeff.dumps(indent) return json.dumps(dic, indent=indent)
[文档] @staticmethod def loads(strs): """ Load JSON(JavaScript Object Notation) into FermionOperator. Args: strs (str): The dumped fermion operator string. Returns: FermionOperator, the FermionOperator load from strings Examples: >>> from mindquantum.core.operators import FermionOperator >>> f = FermionOperator('0', 1 + 2j) + FermionOperator('0^', 'a') >>> obj = FermionOperator.loads(f.dumps()) >>> obj == f True """ _check_input_type('strs', str, strs) dic = json.loads(strs) f_op = FermionOperator() for k, v in dic.items(): f_op += FermionOperator(k, ParameterResolver.loads(v)) return f_op
[文档] def split(self): """ Split the coefficient and the operator. Returns: List[List[ParameterResolver, FermionOperator]], the split result. Examples: >>> from mindquantum.core.operators import FermionOperator >>> a = FermionOperator('0', 'a') + FermionOperator('1^', 1.2) >>> list(a.split()) [[{'a': 1}, const: 0, 1 [0] ], [{}, const: 1.2, 1 [1^] ]] """ for term, coeff in self.terms.items(): yield [coeff, FermionOperator(term)]
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) :] # noqa: E203 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): string = [] for i in term: if i[1] == 1: string.append(f'{i[0]}^') else: string.append(str(i[0])) return ' '.join(string)