Source code for mindquantum.core.operators.qubit_operator

# Copyright 2021 Huawei Technologies Co., Ltd
#
# 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 is the module for the Qubit Operator."""
# pylint: disable=import-error

import copy
import json
import typing

import numpy as np
from scipy.sparse import csr_matrix

import mindquantum as mq
from mindquantum._math.ops import QubitOperator as QubitOperator_
from mindquantum.core.operators._term_value import TermValue
from mindquantum.core.parameterresolver import ParameterResolver, PRConvertible
from mindquantum.dtype.dtype import str_dtype_map
from mindquantum.mqbackend import EQ_TOLERANCE
from mindquantum.utils.type_value_check import (
    _check_and_generate_pr_type,
    _check_int_type,
    _require_package,
)


# pylint: disable=too-many-public-methods
[docs]class QubitOperator(QubitOperator_): """ 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: terms (Union[str, QubitOperator]): The input term of qubit operator. Default: ``None``. coefficient (Union[numbers.Number, str, Dict[str, numbers.Number], 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``. internal (bool): Whether the first argument is internal c++ object of QubitOperator or not. Default: ``False``. 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 [Y3 X0] >>> ham3 = QubitOperator('') >>> ham3 1 [] >>> ham_para = QubitOperator('X0 Y3', 'x') >>> ham_para x [Y3 X0] >>> ham_para.subs({'x':1.2}) 1.2 [Y3 X0] """ def __init__( self, terms: typing.Union[str, "QubitOperator"] = None, coefficient: PRConvertible = 1.0, internal: bool = False, ): """Initialize a QubitOperator instance.""" if terms is None: QubitOperator_.__init__(self) if isinstance(terms, QubitOperator_): internal = True if internal: QubitOperator_.__init__(self, terms) else: QubitOperator_.__init__(self, terms, ParameterResolver(coefficient)) def __len__(self) -> int: """Return the size of term.""" return QubitOperator_.size(self) def __copy__(self) -> "QubitOperator": """Deep copy this QubitOperator.""" return QubitOperator(QubitOperator_.__copy__(self)) def __deepcopy__(self, memodict) -> "QubitOperator": """Deep copy this QubitOperator.""" return QubitOperator(QubitOperator_.__copy__(self)) def __add__(self, other: typing.Union["QubitOperator", PRConvertible]) -> "QubitOperator": """Add a number or a QubitOperator.""" if not isinstance(other, QubitOperator_): return QubitOperator(QubitOperator_.__add__(self, QubitOperator("", ParameterResolver(other)))) return QubitOperator(QubitOperator_.__add__(self, other)) def __iadd__(self, other: typing.Union["QubitOperator", PRConvertible]) -> "QubitOperator": """Add a number or a QubitOperator.""" if not isinstance(other, QubitOperator_): QubitOperator_.__iadd__(self, QubitOperator("", ParameterResolver(other))) return self QubitOperator_.__iadd__(self, other) return self def __radd__(self, other: typing.Union["QubitOperator", PRConvertible]) -> "QubitOperator": """Add a number or a QubitOperator.""" return self + other def __sub__(self, other: typing.Union["QubitOperator", PRConvertible]) -> "QubitOperator": """Sub a number or a QubitOperator.""" return self + (-1 * other) def __isub__(self, other: typing.Union["QubitOperator", PRConvertible]) -> "QubitOperator": """Sub a number or a QubitOperator.""" self += -1 * other return self def __rsub__(self, other: typing.Union["QubitOperator", PRConvertible]) -> "QubitOperator": """Sub a number or a QubitOperator.""" return other + (-1 * self) def __neg__(self): """Return negative QubitOperator.""" return 0 - self def __mul__(self, other: typing.Union["QubitOperator", PRConvertible]) -> "QubitOperator": """Multiply a number or a QubitOperator.""" if not isinstance(other, QubitOperator_): return QubitOperator(QubitOperator_.__mul__(self, QubitOperator("", ParameterResolver(other)))) return QubitOperator(QubitOperator_.__mul__(self, other)) def __imul__(self, other: typing.Union["QubitOperator", PRConvertible]) -> "QubitOperator": """Multiply a number or a QubitOperator.""" if not isinstance(other, QubitOperator_): QubitOperator_.__imul__(self, QubitOperator("", ParameterResolver(other))) return self QubitOperator_.__imul__(self, other) return self def __rmul__(self, other: typing.Union["QubitOperator", PRConvertible]) -> "QubitOperator": """Multiply a number or a QubitOperator.""" return self * other def __truediv__(self, other: PRConvertible) -> "QubitOperator": """Divide a number.""" if other == 0.0: raise ZeroDivisionError("other cannot be zero.") return self * (1.0 / other) def __itruediv__(self, other: PRConvertible) -> "QubitOperator": """Divide a number.""" if other == 0.0: raise ZeroDivisionError("other cannot be zero.") self.__imul__(1.0 / other) return self def __eq__(self, other: typing.Union["QubitOperator", PRConvertible]) -> bool: """Check whether two QubitOperator equal or not.""" if not isinstance(other, QubitOperator_): other = ParameterResolver(other, dtype=self.dtype) other = QubitOperator("", other) return not (self - other).size def __iter__(self) -> typing.Generator["QubitOperator", None, None]: """Iterate every single term.""" for coeff, term in self.split(): yield term * coeff def __pow__(self, frac) -> "QubitOperator": """Power of QubitOperator.""" if not frac: return QubitOperator("").astype(self.dtype) out = 1 * self for _ in range(frac - 1): out *= self return out def __repr__(self) -> str: """Return string expression of a QubitOperator.""" values = [] terms = [] max_value_len = 0 for term, value in self.terms.items(): values.append(value.expression()) max_value_len = max(max_value_len, len(values[-1])) terms.append("[" + ' '.join(f"{j}{i}" for i, j in term) + "]") for i, j in enumerate(values): values[i] = j.rjust(max_value_len) if i != len(values) - 1: terms[i] += " +" if values: return "\n".join(f'{v} {t}' for v, t in zip(values, terms)) return "0" def __str__(self) -> str: """Return string expression of a QubitOperator.""" return self.__repr__() def __getstate__(self): """Get state of parameter resolver.""" return {'json_str': self.dumps()} def __setstate__(self, state): """Set state of parameter resolver.""" a = QubitOperator.loads(state['json_str']) self.__init__(a) @property def dtype(self): """Get the data type of QubitOperator.""" return QubitOperator_.dtype(self) @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] """ return QubitOperator(QubitOperator_.imag(self), internal=True) @property def is_complex(self) -> bool: """Return whether the QubitOperator instance is currently using complex coefficients.""" return self.dtype in (mq.complex128, mq.complex64) @property def is_singlet(self) -> bool: """ To verify whether this operator has only one term. Returns: bool, whether this operator has only one term. """ return QubitOperator_.is_singlet(self) @property def parameterized(self) -> bool: """Check whether this QubitOperator is parameterized.""" return QubitOperator_.parameterized(self) @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] """ return QubitOperator(QubitOperator_.real(self), internal=True) @property def size(self) -> int: """Return the number of terms of this QubitOperator.""" return len(self) @property def terms(self) -> typing.Dict[typing.Tuple[int, str], ParameterResolver]: """Get the terms of a QubitOperator.""" origin_dict = QubitOperator_.get_terms(self) out = {} for key, value in origin_dict: out_key = [] for idx, t in key: out_key.append((idx, str(t))) out[tuple(out_key)] = ParameterResolver(value, internal=True) return out
[docs] @staticmethod def from_openfermion(of_ops) -> "QubitOperator": """ 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 try: from openfermion import QubitOperator as OFQubitOperator except (ImportError, AttributeError): _require_package("openfermion", "1.5.0") if not isinstance(of_ops, OFQubitOperator): raise TypeError( "of_ops should be a QubitOperator" f" from openfermion framework, but get type {type(of_ops)}" ) out = QubitOperator() for term, v in of_ops.terms.items(): out += QubitOperator(' '.join([f"{j}{i}" for i, j in term]), ParameterResolver(v)) return out
[docs] @staticmethod def loads(strs: str) -> "QubitOperator": """ Load JSON(JavaScript Object Notation) into a QubitOperator. Args: strs (str): The dumped fermion operator string. Returns: QubitOperator, the QubitOperator loaded from JSON-formatted strings. Examples: >>> from mindquantum.core.operators import QubitOperator >>> f = QubitOperator('0', 1 + 2j) + QubitOperator('0^', 'a') >>> obj = QubitOperator.loads(f.dumps()) >>> obj == f True """ dic = json.loads(strs) out = QubitOperator().astype(str_dtype_map[dic['dtype']]) for c, t in zip(dic['values'], dic['terms']): out += QubitOperator(t, ParameterResolver.loads(c)) return out
[docs] def astype(self, dtype) -> "QubitOperator": """ Convert to different data type. Note: Converting a complex type QubitOperator to real type will ignore the image part of coefficient. Args: dtype (mindquantum.dtype): new data type of fermion operator. Returns: QubitOperator, new fermion operator with given data type. Examples: >>> from mindquantum.core.operators import QubitOperator >>> import mindquantum as mq >>> f = QubitOperator('X0', 2 + 3j) >>> f.dtype mindquantum.complex128 >>> f.astype(mq.float64) 2 [X0] """ return QubitOperator(QubitOperator_.astype(self, dtype))
[docs] def cast_complex(self) -> "QubitOperator": """Cast a QubitOperator into its complex equivalent.""" new_type = self.dtype if new_type == mq.float32: new_type = mq.complex64 elif new_type == mq.float64: new_type = mq.complex128 return self.astype(new_type)
[docs] def compress(self, abs_tol=EQ_TOLERANCE) -> "QubitOperator": """ Eliminate the very small pauli string that close to zero. Args: abs_tol(float): Absolute tolerance, must be at least 0.0. Default: EQ_TOLERANCE. Returns: QubitOperator, the compressed operator. Examples: >>> from mindquantum.core.operators import QubitOperator >>> ham_compress = QubitOperator('X0 Y1', 0.5) + QubitOperator('Z1 X3', 1e-7) >>> ham_compress 1/2 [Y1 X0] + 1/10000000 [X3 Z1] >>> ham_compress.compress(1e-6) 1/2 [Y1 X0] >>> ham_para_compress = QubitOperator('X0 Y1', 0.5) + QubitOperator('Z5', 'X') >>> ham_para_compress 1/2 [Y1 X0] + X [Z5] >>> ham_para_compress.compress(1e-7) 1/2 [Y1 X0] + X [Z5] """ out = QubitOperator() for k, v in self.terms.items(): if not (v.is_const() and np.abs(v.const) < abs_tol): out += QubitOperator(" ".join(f"{j}{i}" for i, j in k), v) return out
[docs] def count_gates(self): """ Return the gate number when treated in single Hamiltonian. Returns: int, number of the single qubit quantum gates. Examples: >>> from mindquantum.core.operators import QubitOperator >>> a = QubitOperator("X0 Y1") + QubitOperator("X2 Z3") >>> a.count_gates() 4 """ return sum(len(t) for t in self.terms)
[docs] def count_qubits(self) -> int: """ Calculate the number of qubits on which operator acts before removing the unused qubit. Returns: int, the qubits number before remove unused qubit. Examples: >>> from mindquantum.core.operators import QubitOperator >>> a = QubitOperator("Z0 Y3") >>> a.count_qubits() 4 """ return QubitOperator_.count_qubits(self)
[docs] def dumps(self, indent: int = 4) -> str: r""" Dump a 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 (str), the JSON strings of this QubitOperator Examples: >>> from mindquantum.core.operators import QubitOperator >>> f = QubitOperator('0', 1 + 2j) + QubitOperator('0^', 'a') >>> len(f.dumps()) 581 """ out = {} out['dtype'] = str(self.dtype) out['terms'] = [] out['values'] = [] for k, v in self.terms.items(): out["values"].append(v.dumps(indent)) out["terms"].append(" ".join(f"{str(j)}{i}" for i, j in k)) return json.dumps(out, indent=indent)
[docs] def get_coeff(self, term) -> ParameterResolver: """ Get coefficient of given term. Args: term (List[Tuple[int, Union[int, str]]]): the term you want get coefficient. Examples: >>> from mindquantum.core.operators import QubitOperator >>> q = QubitOperator('X0 Y1', 1.2) >>> q.get_coeff([(1, 'Y'), (0, 'X')]) ParameterResolver(dtype: float64, const: 1.200000) """ return ParameterResolver(QubitOperator_.get_coeff(self, [(i, TermValue[j]) for i, j in term]), internal=True)
[docs] def hermitian(self) -> "QubitOperator": """ Get the hermitian of a QubitOperator. Returns: QubitOperator, the hermitian of this QubitOperator. Examples: >>> from mindquantum.core.operators import QubitOperator >>> a = QubitOperator("X0 Y1", {"a": 1 + 2j}) >>> a.hermitian() (-1 + 2j)*a [1 0^] """ return QubitOperator(QubitOperator_.hermitian_conjugated(self), internal=True)
[docs] def matrix(self, n_qubits: int = None, pr=None): """ 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``. pr (ParameterResolver, dict, numpy.ndarray, list, numbers.Number): The parameter resolver for parameterized QubitOperator. Default: None. """ if pr is None: pr = ParameterResolver() pr = _check_and_generate_pr_type(pr, self.params_name) ops = self if self.parameterized: ops = copy.copy(self) ops = ops.subs(pr) if n_qubits is None: n_qubits = -1 _check_int_type('n_qubits', n_qubits) csr = QubitOperator_.sparsing(ops, n_qubits) data = np.array(csr.data, copy=False) indptr = np.array(csr.get_indptr(), copy=False) indices = np.array(csr.get_indices(), copy=False) return csr_matrix((data, indices, indptr), (csr.n_row, csr.n_col))
@property def params_name(self): """Get all parameters of this operator.""" names = [] for pr in self.terms.values(): names.extend([i for i in pr.params_name if i not in names]) return names
[docs] def relabel(self, logic_qubits: typing.List[int]) -> "QubitOperator": """ Relabel the qubit according to the given logic qubits order. Args: logic_qubits (List[int]): The label of logic qubits. For example, if logic_qubits is `[2, 0, 1]`, original qubit `0` will label as `2`. Examples: >>> from mindquantum.core.operators import QubitOperator >>> o = QubitOperator('Z0 Y1 X2 Z3') >>> o 1 [Z0 Y1 X2 Z3] >>> o.relabel([1, 3, 0, 2]) 1 [X0 Z1 Z2 Y3] """ terms = [(tuple((logic_qubits[idx], dag) for idx, dag in key), value) for key, value in self.terms.items()] return QubitOperator(terms, internal=True)
[docs] def singlet(self) -> typing.List["QubitOperator"]: """ Split the single string operator into every word. Returns: List[QubitOperator], The split word of the string. Raises: RuntimeError: if the size of terms is not equal to 1. Examples: >>> from mindquantum.core.operators import QubitOperator >>> ops = QubitOperator("1^ 2", 1) >>> print(ops.singlet()) [1 [2], 1 [1^]] """ return [QubitOperator(i, internal=True) for i in QubitOperator_.singlet(self)]
[docs] def singlet_coeff(self) -> ParameterResolver: """ Get the coefficient of this operator, if the operator has only one term. Returns: ParameterResolver, the coefficient of this single string operator. Raises: RuntimeError: if the size of terms is not equal to 1. Examples: >>> from mindquantum.core.operators import QubitOperator >>> ops = QubitOperator("X0 Y1", "a") >>> print(ops) -a [2 1^] >>> print(ops.singlet_coeff()) -a """ return ParameterResolver(QubitOperator_.singlet_coeff(self), internal=True)
[docs] 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) >>> for i, j in a.split(): ... print(f"{i}, {j}") a, 1 [X0] 1.2, 1 [Z1] """ for i, j in QubitOperator_.split(self): yield ParameterResolver(i, internal=True), QubitOperator(j, internal=True)
[docs] def subs(self, params_value: PRConvertible) -> "QubitOperator": """ Replace the symbolical representation with the corresponding value. Args: params_value (Union[Dict[str, numbers.Number], ParameterResolver]): the value of variable in coefficient. Examples: >>> from mindquantum.core.operators import QubitOperator >>> from mindquantum.core.parameterresolver import ParameterResolver >>> q = QubitOperator('X0', ParameterResolver({'a': 2.0}, 3.0)) >>> q 2*a + 3 [X0] >>> q.subs({'a': 1.5}) 6 [X0] """ if not isinstance(params_value, ParameterResolver): params_value = ParameterResolver(params_value) out = copy.copy(self) QubitOperator_.subs(out, params_value) return out
[docs] def to_openfermion(self): """Convert qubit operator to openfermion format.""" # pylint: disable=import-outside-toplevel try: from openfermion import QubitOperator as OFQubitOperator except (ImportError, AttributeError): _require_package("openfermion", "1.5.0") if self.parameterized: raise ValueError("Cannot not QubitOperator to OpenFermion format.") terms = {} for i, j in self.terms.items(): terms[i] = j.const out = OFQubitOperator() out.terms = terms return out