# 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 numpy as np
from mindquantum.parameterresolver import ParameterResolver as PR
[docs]class BasicGate():
"""BasicGate is the base class of all gaets."""
def __init__(self, name, isparameter=False):
if not isinstance(name, str):
raise TypeError("Excepted string for gate name, get {}".format(
type(name)))
self.name = name
self.isparameter = isparameter
self.str = self.name
self.projectq_gate = None
[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."""
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(self.name, obj_str, ctrl_str)
else:
self.str = "{}({})".format(
self.name, ' '.join([str(i) for i in self.obj_qubits]))
[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. Defaults to None.
Returns:
Gate, Return a new gate.
Examples:
>>> x = X.on(1)
>>> x.obj_qubits
[1]
>>> x.ctrl_qubits
[]
>>> x = X.on(2, [0, 1])
>>> x.ctrl_qubits
[0, 1]
"""
obj_qubits_old = deepcopy(obj_qubits)
ctrl_qubits_old = deepcopy(ctrl_qubits)
if isinstance(obj_qubits, int):
self.obj_qubits = [obj_qubits]
_check_qubit_id(obj_qubits)
elif isinstance(obj_qubits, Iterable):
for i in obj_qubits:
_check_qubit_id(i)
self.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:
self.ctrl_qubits = []
else:
if isinstance(ctrl_qubits, int):
self.ctrl_qubits = [ctrl_qubits]
_check_qubit_id(ctrl_qubits)
elif isinstance(ctrl_qubits, Iterable):
for i in ctrl_qubits:
_check_qubit_id(i)
self.ctrl_qubits = list(ctrl_qubits)
else:
raise TypeError("Excepted int, list or tuple for \
ctrl_qubits, but get {}".format(type(obj_qubits)))
new = deepcopy(self)
new.generate_description()
self.obj_qubits = obj_qubits_old
self.ctrl_qubits = ctrl_qubits_old
return new
def requires_grad(self):
return self
def no_grad(self):
return self
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.isparameter != other.isparameter 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."""
def __init__(self, name):
BasicGate.__init__(self, name, False)
self.coeff = None
self.matrix_value = None
[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.matrix_value = np.conj(self.matrix_value.T)
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]class ParameterGate(NoneParameterGate, BasicGate):
"""The basic class of gate that is parameterized."""
def __init__(self, name, coeff=None):
if isinstance(coeff, (int, float, complex)):
NoneParameterGate.__init__(self, name)
self.coeff = coeff
self.str = self.str + "({})".format(round(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])))
def generate_description(self):
BasicGate.generate_description(self)
if self.isparameter:
self.str = self.str[:len(
self.name) + 1] + ' '.join(self.coeff.keys())\
+ '|' + self.str[len(self.name) + 1:]
else:
self.str = self.str[:len(
self.name) + 1] + str(round(self.coeff, 3))\
+ ',' + self.str[len(self.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.
"""
self.coeff.requires_grad()
return self
[docs] def no_grad(self):
"""
All parameters do not need grad.
"""
self.coeff.no_grad()
return self
[docs] def requires_grad_part(self, names):
"""
Set certain parameters that need grad.
Args:
names (tuple[str]): Parameters that requires grad.
"""
self.coeff.requires_grad_part(names)
return self
[docs] def no_grad_part(self, names):
"""
Set certain parameters that not need grad.
Args:
names (tuple[str]): Parameters that not requires grad.
"""
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.
Examples:
>>> rx1 = RX(1.2)
>>> rx1
RX(1.2)
>>> 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)
[docs] def hermitian(self):
"""
Get the mermitian gate of this parameterized gate.
Note:
We only set the coeff to -coeff.
"""
hermitian_gate = deepcopy(self)
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.
Returns:
np.array, Return the numpy array of the matrix.
Examples:
>>> 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.isparameter:
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:
about_what (str, optional): Specific the differential is about
which parameter. Defaults to None.
Returns:
numpy.ndarray, Return the numpy array of the differential matrix.
Examples:
>>> 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.isparameter:
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!")
def _check_gate_type(gate):
if not isinstance(gate, BasicGate):
raise TypeError("Require a quantum gate, but get {}".format(
type(gate)))
def _check_qubit_id(qubit_id):
if not isinstance(qubit_id, int):
raise TypeError(
"Qubit should be a non negative int, but get {}!".format(
type(qubit_id)))
if qubit_id < 0:
raise ValueError(
"Qubit should be non negative int, but get {}!".format(qubit_id))