# Portions Copyright (c) 2020 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 we develop is default being licensed under Apache 2.0 license,
# and also uses or refactor Fermilib and OpenFermion licensed under
# Apache 2.0 license.
"""This is the module for the Qubit Operator."""
import json
import numpy as np
from scipy.sparse import csr_matrix, kron
from ...utils.type_value_check import _check_input_type, _check_int_type
from ..operators._base_operator import _Operator
from ..parameterresolver import ParameterResolver
EQ_TOLERANCE = 1e-8
# Define products of all Pauli operators for symbolic multiplication.
# Note can translate all the lowercase to uppercase 'i'->'I'
_PAULI_OPERATOR_PRODUCTS = {
('I', 'I'): (1.0, 'I'),
('I', 'X'): (1.0, 'X'),
('I', 'Y'): (1.0, 'Y'),
('I', 'Z'): (1.0, 'Z'),
('X', 'I'): (1.0, 'X'),
('X', 'X'): (1.0, 'I'),
('X', 'Y'): (1.0j, 'Z'),
('X', 'Z'): (-1.0j, 'Y'),
('Y', 'I'): (1.0, 'Y'),
('Y', 'X'): (-1.0j, 'Z'),
('Y', 'Y'): (1.0, 'I'),
('Y', 'Z'): (1.0j, 'X'),
('Z', 'I'): (1.0, 'Z'),
('Z', 'X'): (1.0j, 'Y'),
('Z', 'Y'): (-1.0j, 'X'),
('Z', 'Z'): (1.0, 'I'),
}
def _check_valid_qubit_operator_term(qo_term):
"""Check valid qubit operator term."""
if qo_term is not None and qo_term != '':
if not isinstance(qo_term, (str, tuple)):
raise ValueError(f'Qubit operator requires a string or a tuple, but get {type(qo_term)}')
operators = ('X', 'Y', 'Z')
if isinstance(qo_term, str):
terms = qo_term.split(' ')
for term in terms:
if len(term) < 2 or term[0].upper() not in operators or not term[1:].isdigit():
if term:
raise ValueError(f'Invalid qubit operator term {term}.')
if isinstance(qo_term, tuple):
for term in qo_term:
if len(term) != 2 or not isinstance(term[0], int) or term[0] < 0 or term[1].upper() not in operators:
raise ValueError(f'Invalid qubit operator term {term}.')
[文档]class QubitOperator(_Operator):
"""
A sum of terms acting on qubits, e.g., 0.5 * 'X1 X5' + 0.3 * 'Z1 Z2'.
A term is an operator acting on n qubits and can be represented as:
coefficient * local_operator[0] x ... x local_operator[n-1]
where x is the tensor product. A local operator is a Pauli operator
('I', 'X', 'Y', or 'Z') which acts on one qubit. In mathematical notation
a QubitOperator term is, for example, 0.5 * 'X1 X5', which means that a Pauli X operator acts
on qubit 1 and 5, while the identity operator acts on all the rest qubits.
Note that a Hamiltonian composed of QubitOperators should be a hermitian
operator, thus requires the coefficients of all terms must be real.
QubitOperator has the following attributes set as follows:
operators = ('X', 'Y', 'Z'), different_indices_commute = True.
Args:
term (str): The input term of qubit operator. Default: None.
coefficient (Union[numbers.Number, str, ParameterResolver]): The
coefficient of this qubit operator, could be a number or a variable
represent by a string or a symbol or a parameter resolver. Default: 1.0.
Examples:
>>> from mindquantum.core.operators import QubitOperator
>>> ham = ((QubitOperator('X0 Y3', 0.5)
... + 0.6 * QubitOperator('X0 Y3')))
>>> ham2 = QubitOperator('X0 Y3', 0.5)
>>> ham2 += 0.6 * QubitOperator('X0 Y3')
>>> ham2
1.1 [X0 Y3]
>>> ham3 = QubitOperator('')
>>> ham3
1 []
>>> ham_para = QubitOperator('X0 Y3', 'x')
>>> ham_para
x [X0 Y3]
>>> ham_para.subs({'x':1.2})
6/5 [X0 Y3]
"""
__hash__ = None
def __init__(self, term=None, coefficient=1.0):
"""Initialize a QubitOperator object."""
super().__init__(term, coefficient)
_check_valid_qubit_operator_term(term)
self.operators = ('X', 'Y', 'Z')
self.gates_number = 0
self.qubit_type = True
if term is not None:
if term == '':
term = self._parse_term(())
else:
term = self._parse_term(term)
self.coefficient, term = self._simplify(term, self.coefficient)
self.terms[term] = self.coefficient
[文档] def count_gates(self):
"""
Return the gate number when treated in single Hamiltonian.
Returns:
int, number of the single qubit quantum gates.
"""
self.gates_number = 0
for operator in self.terms:
n_local_operator = len(operator)
self.gates_number += n_local_operator
return self.gates_number
[文档] def to_openfermion(self):
"""Convert qubit operator to openfermion format."""
# pylint: disable=import-outside-toplevel
from openfermion import QubitOperator as OFQubitOperator
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
qubit_operator = OFQubitOperator()
qubit_operator.terms = terms
return qubit_operator
[文档] @staticmethod
def from_openfermion(of_ops):
"""
Convert qubit operator from openfermion to mindquantum format.
Args:
of_ops (openfermion.QubitOperator): Qubit operator from openfermion.
Returns:
QubitOperator, qubit operator from mindquantum.
"""
# pylint: disable=import-outside-toplevel
from openfermion import QubitOperator as OFQubitOperator
_check_input_type('of_ops', OFQubitOperator, of_ops)
qubit_operator = QubitOperator()
for k, v in of_ops.terms.items():
qubit_operator.terms[k] = ParameterResolver(v)
return qubit_operator
def _parse_string(self, terms_string):
"""Parse a term given as a string type.
e.g. For QubitOperator:
'X2 Y0 Z3' -> ((0, 'Y'),(2, 'X'), (3,'Z'))
Returns:
tuple, such as ((0, 'Y'),(2, 'X'), (3,'Z'))
Raises:
'XX2' or '1.5 X2' is not the proper format and
could raise TypeError.
"""
terms = terms_string.split()
terms_to_tuple = []
for sub_term in terms:
operator = sub_term[0]
index = sub_term[1:]
if operator.upper() not in self.operators:
raise ValueError(
f'Invalid type of operator {operator}.'
'The Qubit Pauli operator should be one of this {self.operators}'
)
if not index.isdigit() or int(index) < 0:
raise ValueError(f"Invalid index {self.operators}.The qubit index should be nonnegative integer")
terms_to_tuple.append((int(index), operator))
terms_to_tuple = sorted(terms_to_tuple, key=lambda item: item[0])
return tuple(terms_to_tuple)
[文档] def matrix(self, n_qubits=None): # pylint: disable=too-many-locals
"""
Convert this qubit operator to csr_matrix.
Args:
n_qubits (int): The total qubits of final matrix. If None, the value will be
the maximum local qubit number. Default: None.
"""
from mindquantum import ( # pylint: disable=import-outside-toplevel,cyclic-import
I,
X,
Y,
Z,
)
pauli_map = {
'X': csr_matrix(X.matrix().astype(np.complex128)),
'Y': csr_matrix(Y.matrix().astype(np.complex128)),
'Z': csr_matrix(Z.matrix().astype(np.complex128)),
'I': csr_matrix(I.matrix().astype(np.complex128)),
}
if not self.terms:
raise ValueError("Cannot convert empty qubit operator to matrix")
n_qubits_local = 0
for term in self.terms:
for idx, _ in term:
n_qubits_local = max(n_qubits_local, idx + 1)
if n_qubits_local == 0 and n_qubits is None:
raise ValueError("You should specific n_qubits for converting a identity qubit 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 qubit 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 qubit operator to matrix.")
coeff = coeff.const
if not term:
out += csr_matrix(np.identity(2**n_qubits, dtype=np.complex128)) * coeff
else:
tmp = np.array([1], dtype=np.complex128) * coeff
total = [pauli_map['I'] for _ in range(n_qubits)]
for idx, local_op in term:
total[idx] = pauli_map[local_op]
for i in total:
tmp = kron(i, tmp)
out += tmp
return out
@property
def real(self):
"""
Convert the coefficient to its real part.
Returns:
QubitOperator, the real part of this qubit operator.
Examples:
>>> from mindquantum.core.operators import QubitOperator
>>> f = QubitOperator('X0', 1 + 2j) + QubitOperator('Y0', 'a')
>>> f.real.compress()
1 [X0] +
a [Y0]
"""
out = QubitOperator()
for k, v in self.terms.items():
out.terms[k] = v.real
return out
@property
def imag(self):
"""
Convert the coefficient to its imag part.
Returns:
QubitOperator, the imag part of this qubit operator.
Examples:
>>> from mindquantum.core.operators import QubitOperator
>>> f = QubitOperator('X0', 1 + 2j) + QubitOperator('Y0', 'a')
>>> f.imag.compress()
2 [X0]
"""
out = QubitOperator()
for k, v in self.terms.items():
out.terms[k] = v.imag
return out
def _simplify(self, terms, coefficient=1.0):
r"""Simplify the list by using the commuation and anti-commutation relationship.
Args:
terms (str, list((int, str),), tuple((int, str),)): The input terms_lst could be a sorted list or unsorted
list e.g. [(3, 'Z'),(2, 'X'), (3,'Z')] -> [(2,'X')]
Also, it could accept input with tuples ((3, 'Z'),(2, 'X'), (3,'Z')) ->[(2,'X')]
coefficient (int, float, complex, str, ParameterResolver): The coefficient for the corresponding single
operators
Returns:
tuple(coefficient, tuple(reduced_terms)), the simplified coefficient and operators
"""
if not terms:
return coefficient, terms
if isinstance(terms, dict):
terms = list(terms)
elif isinstance(terms[0], int):
return coefficient, tuple(terms)
else:
terms = sorted(terms, key=lambda term: term[0])
reduced_terms = []
left_term = terms[0]
for right_term in terms[1:]:
left_index, left_operator = left_term
right_index, right_operator = right_term
left_operator, right_operator = left_operator.upper(), right_operator.upper()
if left_index == right_index:
new_coefficient, new_operator = _PAULI_OPERATOR_PRODUCTS[(left_operator, right_operator)]
left_term = (left_index, new_operator)
coefficient *= new_coefficient
else:
if left_term[1].upper() != 'I':
reduced_terms.append((left_term[0], left_term[1].upper()))
left_term = right_term
if left_term[1].upper() != 'I':
reduced_terms.append((left_term[0], left_term[1].upper()))
return coefficient, tuple(reduced_terms)
def __str__(self):
"""Return an easy-to-read string representation of the QubitOperator."""
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()} ['
else:
tmp_string = f'{coeff} ['
# deal with this situation (1,'X') or [1, 'X']
if term == ():
tmp_string.join(' ]')
elif isinstance(term[0], int):
index, operator = term
tmp_string += f'{operator}{index} '
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'{operator}{index} '
if term_cnt < len(self.terms):
string_rep += f'{tmp_string.strip()}] +\n'
else:
string_rep += f'{tmp_string.strip()}] '
return string_rep
def __repr__(self):
"""Return a string representation of the object."""
return str(self)
[文档] def dumps(self, indent=4):
r"""
Dump QubitOperator 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(strings), the JSON strings of QubitOperator
Examples:
>>> from mindquantum.core.operators import QubitOperator
>>> ops = QubitOperator('X0 Y1', 1.2) + QubitOperator('Z0 X1', {'a': 2.1})
>>> len(ops.dumps())
448
"""
if indent is not None:
_check_int_type('indent', indent)
dic = {}
for term, coeff in self.terms.items():
string = _qubit_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 QubitOperator.
Args:
strs (str): The dumped qubit operator string.
Returns:
QubitOperator`, the QubitOperator load from strings
Examples:
>>> from mindquantum.core.operators import QubitOperator
>>> ops = QubitOperator('X0 Y1', 1.2) + QubitOperator('Z0 X1', {'a': 2.1})
>>> obj = QubitOperator.loads(ops.dumps())
>>> obj == ops
True
"""
_check_input_type('strs', str, strs)
dic = json.loads(strs)
f_op = QubitOperator()
for k, v in dic.items():
f_op += QubitOperator(k, ParameterResolver.loads(v))
return f_op
[文档] def split(self):
"""
Split the coefficient and the operator.
Returns:
List[List[ParameterResolver, QubitOperator]], the split result.
Examples:
>>> from mindquantum.core.operators import QubitOperator
>>> a = QubitOperator('X0', 'a') + QubitOperator('Z1', 1.2)
>>> list(a.split())
[[{'a': 1}, const: 0, 1 [X0] ], [{}, const: 1.2, 1 [Z1] ]]
"""
for i, j in self.terms.items():
yield [j, QubitOperator(i)]
def _qubit_tuple_to_string(term):
string = []
for i in term:
string.append(f'{i[1]}{i[0]}')
return ' '.join(string)
__all__ = ['QubitOperator']