Source code for mindquantum.core.gates.basic

# -*- coding: utf-8 -*-
# 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.
# ============================================================================
"""Basic module for quantum gate."""

import warnings
from copy import deepcopy
from abc import abstractmethod
from collections.abc import Iterable
import numbers
import numpy as np
from mindquantum.core.parameterresolver import ParameterResolver as PR
from mindquantum.utils.f import _common_exp
from mindquantum import mqbackend as mb
from mindquantum.utils.type_value_check import _check_gate_type
from mindquantum.utils.type_value_check import _check_qubit_id
from mindquantum.utils.type_value_check import _check_obj_and_ctrl_qubits

HERMITIAN_PROPERTIES = {
    'self_hermitian': 0,  # the hermitian of this gate is its self
    'do_hermitian': 1,  # just do hermitian when you need hermitian
    'params_opposite': 2  # use the negative parameters for hermitian
}


[docs]class BasicGate(): """ BasicGate is the base class of all gates. Args: name (str): the name of this gate. parameterized (bool): whether this is a parameterized gate. Default: False. """ def __init__(self, name, parameterized=False): if not isinstance(name, str): raise TypeError("Excepted string for gate name, get {}".format(type(name))) self.name = name self.parameterized = parameterized self.str = self.name self.projectq_gate = None self.obj_qubits = [] self.ctrl_qubits = [] self.hermitian_property = HERMITIAN_PROPERTIES['self_hermitian'] self.daggered = False
[docs] @abstractmethod def matrix(self, *args): """The matrix of the gate."""
[docs] @abstractmethod def hermitian(self): """Return the hermitian gate of this gate."""
[docs] @abstractmethod def define_projectq_gate(self): """Define the corresponded projectq gate."""
[docs] def generate_description(self): """Description generator.""" name = self.name if self.hermitian_property == HERMITIAN_PROPERTIES['do_hermitian'] and self.daggered: name += '†' if self.ctrl_qubits: obj_str = ' '.join([str(i) for i in self.obj_qubits]) ctrl_str = ' '.join([str(i) for i in self.ctrl_qubits]) self.str = "{}({} <-: {})".format(name, obj_str, ctrl_str) elif self.obj_qubits: self.str = "{}({})".format(name, ' '.join([str(i) for i in self.obj_qubits])) else: self.str = name
[docs] def on(self, obj_qubits, ctrl_qubits=None): """ Define which qubit the gate act on and the control qubit. Note: In this framework, the qubit that the gate act on is specified first, even for control gate, e.g. CNOT, the second arg is control qubits. Args: obj_qubits (int, list[int]): Specific which qubits the gate act on. ctrl_qubits (int, list[int]): Specific the control qbits. Default, None. Returns: Gate, Return a new gate. Examples: >>> from mindquantum.core.gates import X >>> x = X.on(1) >>> x.obj_qubits [1] >>> x.ctrl_qubits [] >>> x = X.on(2, [0, 1]) >>> x.ctrl_qubits [0, 1] """ new = deepcopy(self) if isinstance(obj_qubits, (int, np.int64)): new.obj_qubits = [obj_qubits] _check_qubit_id(obj_qubits) elif isinstance(obj_qubits, Iterable): for i in obj_qubits: _check_qubit_id(i) new.obj_qubits = list(obj_qubits) else: raise TypeError("Excepted int, list or tuple for \ obj_qubits, but get {}".format(type(obj_qubits))) if ctrl_qubits is None: new.ctrl_qubits = [] else: if isinstance(ctrl_qubits, (int, np.int64)): new.ctrl_qubits = [ctrl_qubits] _check_qubit_id(ctrl_qubits) elif isinstance(ctrl_qubits, Iterable): for i in ctrl_qubits: _check_qubit_id(i) new.ctrl_qubits = list(ctrl_qubits) else: raise TypeError("Excepted int, list or tuple for \ ctrl_qubits, but get {}".format(type(obj_qubits))) new.generate_description() new.check_obj_qubits() _check_obj_and_ctrl_qubits(new.obj_qubits, new.ctrl_qubits) return new
def requires_grad(self): return self def no_grad(self): return self
[docs] @abstractmethod def check_obj_qubits(self): """Check obj qubit number"""
def __str__(self): return self.str def __repr__(self): return self.__str__() def __eq__(self, other): _check_gate_type(other) if self.name != other.name or \ self.parameterized != other.parameterized or \ self.obj_qubits != other.obj_qubits or \ self.ctrl_qubits != other.ctrl_qubits: return False return True def __or__(self, qubits): if not isinstance(qubits, tuple): qubits = (qubits, ) qubits = list(qubits) for i, _ in enumerate(qubits): if hasattr(qubits[i], "qubit_id"): qubits[i] = [qubits[i]] ctrls = [] objs = [] if len(qubits) == 1: objs = [qubit.qubit_id for qubit in qubits[0]] else: ctrls = [qubit.qubit_id for qubit in qubits[0]] objs = [qubit.qubit_id for qubit in qubits[1]] qubits[0][0].circuit_.append(self.on(objs, ctrls))
[docs]class NoneParameterGate(BasicGate): """ The basic class of gate that is not parametrized. Args: name (str): The name of the this gate. """ def __init__(self, name): BasicGate.__init__(self, name, False) self.coeff = None self.matrix_value = None
[docs] def get_cpp_obj(self): """Get the cpp obj of this gate.""" cpp_gate = mb.get_gate_by_name(self.name) cpp_gate.obj_qubits = self.obj_qubits cpp_gate.ctrl_qubits = self.ctrl_qubits if self.daggered: cpp_gate.daggered = True if self.hermitian_property == HERMITIAN_PROPERTIES["do_hermitian"]: cpp_gate.base_matrix = mb.dim2matrix(self.matrix()) if self.hermitian_property == HERMITIAN_PROPERTIES["params_opposite"]: raise ValueError(f"Hermitian properties of None parameterized gate {self} can not be params_opposite") return cpp_gate
[docs] def matrix(self, *args): """ Get the matrix of this none parameterized gate. """ return self.matrix_value
[docs] def hermitian(self): """ Get hermitian gate of this none parameterized gate. """ hermitian_gate = deepcopy(self) hermitian_gate.daggered = not hermitian_gate.daggered if self.hermitian_property == HERMITIAN_PROPERTIES["do_hermitian"]: hermitian_gate.matrix_value = np.conj(self.matrix_value.T) hermitian_gate.generate_description() return hermitian_gate
def define_projectq_gate(self): raise NotImplementedError def __call__(self, obj_qubits, ctrl_qubits=None): return self.on(obj_qubits, ctrl_qubits)
[docs] def check_obj_qubits(self): """Check obj qubit number""" n_qubits_exp = np.log2(len(self.matrix_value)).astype(int) n_qubits = len(self.obj_qubits) self.n_qubits = n_qubits_exp if n_qubits_exp != n_qubits: raise ValueError(f"obj_qubits of {self.name} requires {n_qubits_exp} qubits, but get {n_qubits}")
[docs]class ParameterGate(NoneParameterGate, BasicGate): """ The basic class of gate that is parameterized. Args: name (str): the name of this gate. coeff (Union[dict, ParameterResolver]): the coefficients of this parameterized gate. Default: None. """ def __init__(self, name, coeff=None): if isinstance(coeff, numbers.Number): NoneParameterGate.__init__(self, name) self.coeff = coeff self.str = self.str + "({})".format(_common_exp(self.coeff, 3)) else: BasicGate.__init__(self, name, True) if coeff is None: warnings.warn("Parameter gate without parameters specified, \ automatically set it to c1.") self.coeff = PR({'c1': 1}) elif not isinstance(coeff, (list, tuple, str, dict, PR)): raise TypeError("Excepted str, list or tuple for coeff, \ but get {}".format(type(coeff))) else: if isinstance(coeff, str): self.coeff = PR({coeff: 1}) elif isinstance(coeff, PR): self.coeff = coeff elif isinstance(coeff, dict): self.coeff = PR(deepcopy(coeff)) else: self.coeff = PR(dict(zip(coeff, [1 for i in coeff]))) self.str = self.str + "({})".format(self.coeff.expression()) def generate_description(self): name = self.name if self.hermitian_property == HERMITIAN_PROPERTIES['do_hermitian'] and self.daggered: name += '†' BasicGate.generate_description(self) if not hasattr(self, 'obj_qubits') or not self.obj_qubits: if self.parameterized: self.str = f'{name}({self.coeff.expression()})' else: self.str = f'{name}({_common_exp(self.coeff, 3)})' else: if self.parameterized: self.str = self.str[:len( name) + 1] + str(self.coeff.expression())\ + '|' + self.str[len(name) + 1:] else: self.str = self.str[:len( name) + 1] + str(_common_exp(self.coeff, 3))\ + '|' + self.str[len(name) + 1:] @abstractmethod def matrix(self, *paras_out): pass @abstractmethod def diff_matrix(self, *paras_out, about_what=None): pass
[docs] @staticmethod def linearcombination(coeff_in, paras_out): """ Combine the parameters and coefficient. Args: coeff_in (Union[dict, ParameterResolver]): the coefficient of the parameterized gate. paras_out (Union[dict, ParameterResolver]): the parameter you send in. Returns: float, Multiply the values of the common keys of these two dicts. """ if not isinstance(coeff_in, (dict, PR)) or not isinstance(paras_out, (dict, PR)): raise TypeError("Require a dict or ParameterResolver for parameters, but get {} and {}!".format( type(coeff_in), type(paras_out))) params = 0 for key, value in coeff_in.items(): if key not in paras_out: raise KeyError("parameter {} not in parameters you send in!".format(key)) params += value * paras_out[key] return params
def __eq__(self, other): if BasicGate.__eq__(self, other): if self.coeff == other.coeff: return True return False
[docs] def requires_grad(self): """ All parameters requires grad. Inplace operation. Returns: BasicGate, a parameterized gate with all parameters need to update gradient. """ if self.parameterized: self.coeff.requires_grad() return self
[docs] def no_grad(self): """ All parameters do not need grad. Inplace operation. Returns: BasicGate, a parameterized gate with all parameters not need to update gradient. """ if self.parameterized: self.coeff.no_grad() return self
[docs] def requires_grad_part(self, names): """ Set certain parameters that need grad. Inplace operation. Args: names (tuple[str]): Parameters that requires grad. Returns: BasicGate, with some part of parameters need to update gradient. """ self.coeff.requires_grad_part(names) return self
[docs] def no_grad_part(self, names): """ Set certain parameters that not need grad. Inplace operation. Args: names (tuple[str]): Parameters that not requires grad. Returns: BasicGate, with some part of parameters not need to update gradient. """ self.coeff.no_grad_part(names) return self
[docs]class IntrinsicOneParaGate(ParameterGate): """ The parameterized gate that can be intrinsicly described by only one parameter. Note: A parameterized gate can also be a non parameterized gate, if the parameter you send in is only a number. Args: name (str): the name of this parameterized gate. coeff (Union[dict, ParameterResolver]): the parameter of this gate. Default: Nnoe. Examples: >>> from mindquantum.core.gates import RX >>> rx1 = RX(1.2) >>> rx1 RX(6/5) >>> rx2 = RX({'a' : 0.5}) >>> rx2.coeff {'a': 0.5} >>> rx2.linearcombination(rx2.coeff,{'a' : 3}) 1.5 """ def __init__(self, name, coeff=None): ParameterGate.__init__(self, name, coeff) self.hermitian_property = HERMITIAN_PROPERTIES['params_opposite']
[docs] def get_cpp_obj(self): """Get cpp obj of this gate.""" cpp_gate = mb.get_gate_by_name(self.name) cpp_gate.obj_qubits = self.obj_qubits cpp_gate.ctrl_qubits = self.ctrl_qubits cpp_gate.daggered = self.daggered if not self.parameterized: cpp_gate.apply_value(self.coeff) else: cpp_gate.params = self.coeff.get_cpp_obj() return cpp_gate
[docs] def hermitian(self): """ Get the hermitian gate of this parameterized gate. Not inplace operation. Note: We only set the coeff to -coeff. Examples: >>> from mindquantum import RX >>> rx = RX({'a': 1+2j}) >>> rx.hermitian() RX(a*(-1.0 - 2.0*I)) """ hermitian_gate = deepcopy(self) hermitian_gate.daggered = not hermitian_gate.daggered hermitian_gate.coeff = 1 * self.coeff if isinstance(self.coeff, PR): hermitian_gate.coeff *= -1 else: hermitian_gate.coeff = -float(self.coeff) hermitian_gate.generate_description() return hermitian_gate
@abstractmethod def _matrix(self, theta): pass @abstractmethod def _diff_matrix(self, theta): pass
[docs] def matrix(self, *paras_out): """ The matrix of parameterized gate. Note: If the parameterized gate convert to non parameterized gate, then you don't need any parameters to get this matrix. Args: paras_out (Union[dict, ParameterResolver]): Parameters of this gate. Returns: numpy.ndarray, Return the numpy array of the matrix. Examples: >>> from mindquantum.core.gates import RX >>> rx1 = RX(0) >>> rx1.matrix() array([[1.+0.j, 0.-0.j], [0.-0.j, 1.+0.j]]) >>> rx2 = RX({'a' : 1.2}) >>> np.round(rx2.matrix({'a': 2}), 2) array([[0.36+0.j , 0. -0.93j], [0. -0.93j, 0.36+0.j ]]) """ if self.parameterized: theta = 0 if isinstance(paras_out[0], dict): theta = self.linearcombination(self.coeff, paras_out[0]) else: if len(self.coeff) != 1: raise Exception("This gate has more than one parameters, \ need a parameters map!") theta = paras_out[0] * list(self.coeff.values())[0] return self._matrix(theta) return self._matrix(self.coeff)
[docs] def diff_matrix(self, *paras_out, about_what=None): """ The differential form of this parameterized gate. Args: paras_out (Union[dict, ParameterResolver]): Parameters of this gate. about_what (str): Specific the differential is about which parameter. Default: None. Returns: numpy.ndarray, Return the numpy array of the differential matrix. Examples: >>> from mindquantum import RX >>> rx = RX('a') >>> np.round(rx.diff_matrix({'a' : 2}), 2) array([[-0.42+0.j , 0. -0.27j], [ 0. -0.27j, -0.42+0.j ]]) """ if self.parameterized: theta = 0 if isinstance(paras_out[0], dict): theta = self.linearcombination(self.coeff, paras_out[0]) else: if len(self.coeff) != 1: raise Exception("This gate has more than one parameters, \ need a parameters map!") theta = paras_out[0] * list(self.coeff.values())[0] if about_what is None: if len(self.coeff) != 1: raise Exception("Please specific the diff is about which parameter.") about_what = list(self.coeff.keys())[0] return self.coeff[about_what] * self._diff_matrix(theta) raise Exception("Not a parameterized gate!")
[docs] def check_obj_qubits(self): """Check obj qubit number""" n_qubits = len(self.obj_qubits) n_qubits_exp = np.log2(len(self._matrix(0))).astype(int) self.n_qubits = n_qubits_exp if n_qubits_exp != n_qubits: raise ValueError(f"obj_qubits of {self.name} requires {n_qubits_exp} qubits, but get {n_qubits}")