#
# Copyright © 2022-2024 University of Strasbourg. All Rights Reserved.
# Copyright © 2023-2025 QPerfect. All Rights Reserved.
#
# 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.
#
"""U (Generic Unitary) gate."""
import mimiqcircuits.operations.gates.gate as mcg
from mimiqcircuits.matrices import umatrix
from mimiqcircuits import numerics as _nm
from mimiqcircuits.operations.utils import control_one_defined
import mimiqcircuits as mc
import numpy as np
import symengine as se
from mimiqcircuits._sympycheck import _is_basic
def _negate(x):
# Adding `0.0` turns a negated float zero into `+0.0`, so that `-0.0` and
# `0.0` serialize to the same bytes.
return -x + 0.0 if isinstance(x, float) else -x
[docs]
class GateU(mcg.Gate):
r"""Single qubit generic unitary phase gate.
**Matrix representation:**
.. math::
\operatorname{U}(\theta, \phi, \lambda, \gamma) =
\mathrm{e}^{i\gamma}
\begin{pmatrix}
\cos\left(\frac{\theta}{2}\right) & -\mathrm{e}^{i\lambda}\sin\left(\frac{\theta}{2}\right)\\
\mathrm{e}^{i\phi}\sin\left(\frac{\theta}{2}\right) & \mathrm{e}^{i(\phi+\lambda)}\cos\left (\frac{\theta}{2}\right)
\end{pmatrix}
Parameters:
theta (float): Euler angle 1 in radians.
phi (float): Euler angle 2 in radians.
lambda (float): Euler angle 3 in radians.
gamma (float, optional): Euler angle 4 in radians (default is 0).
Examples:
>>> from mimiqcircuits import *
>>> from symengine import *
>>> theta, phi, lmbda, gamma = symbols('theta phi lambda gamma')
>>> GateU(theta, phi, lmbda, gamma)
U(theta, phi, lambda, gamma)
>>> GateU(theta, phi, lmbda, gamma).matrix()
[exp(I*gamma)*cos((1/2)*theta), -exp(I*(gamma + lambda))*sin((1/2)*theta)]
[exp(I*(gamma + phi))*sin((1/2)*theta), exp(I*(gamma + lambda + phi))*cos((1/2)*theta)]
<BLANKLINE>
>>> c = Circuit().push(GateU(theta, phi, lmbda, gamma), 0)
>>> c
1-qubit circuit with 1 instruction:
└── U(theta, phi, lambda, gamma) @ q[0]
<BLANKLINE>
>>> GateU(theta, phi, lmbda, gamma).power(2), GateU(theta, phi, lmbda, gamma).inverse()
(U(theta, phi, lambda, gamma)**2, U(-theta, -lambda, -phi, -gamma))
>>> GateU(theta, phi, lmbda, gamma).decompose()
1-qubit circuit with 1 instruction:
└── U(theta, phi, lambda, gamma) @ q[0]
<BLANKLINE>
>>> c = Circuit().push(GateU(theta, phi, lmbda, gamma), 0)
>>> c
1-qubit circuit with 1 instruction:
└── U(theta, phi, lambda, gamma) @ q[0]
<BLANKLINE>
>>> GateU(theta, phi, lmbda, gamma).power(2), GateU(theta, phi, lmbda, gamma).inverse()
(U(theta, phi, lambda, gamma)**2, U(-theta, -lambda, -phi, -gamma))
>>> GateU(theta, phi, lmbda, gamma).decompose()
1-qubit circuit with 1 instruction:
└── U(theta, phi, lambda, gamma) @ q[0]
<BLANKLINE>
"""
_name = "U"
_num_qubits = 1
_qregsizes = [1]
_parnames = ("theta", "phi", "lmbda", "gamma")
[docs]
def __init__(self, theta, phi, lmbda, gamma=0.0):
self.theta = theta
self.phi = phi
self.lmbda = lmbda
self.gamma = gamma
def _matrix(self):
return umatrix(self.theta, self.phi, self.lmbda, self.gamma)
def _matrix_numeric(self, theta, phi, lmbda, gamma):
return _nm.umatrix(theta, phi, lmbda, gamma)
[docs]
def inverse(self):
return GateU(
_negate(self.theta), _negate(self.lmbda), _negate(self.phi), _negate(self.gamma)
)
def _control(self, n):
return control_one_defined(
n, self, mc.GateCU(self.theta, self.phi, self.lmbda, self.gamma)
)
def _power(self, p):
if self.is_symbolic():
return mc.Power(self, p)
def to_numeric(value):
if (
_is_basic(value)
and value == se.pi
or value == se.pi
):
return float(value)
return value
theta_value = to_numeric(self.theta)
phi_value = to_numeric(self.phi)
lambda_value = to_numeric(self.lmbda)
gamma_value = to_numeric(self.gamma)
numeric_gate = GateU(theta_value, phi_value, lambda_value, gamma_value)
matrix = numeric_gate.matrix().tolist()
matrix_np = np.array(self.convert_to_numeric(matrix))
from scipy.linalg import expm, logm
pow_matrix = expm(float(p) * logm(matrix_np))
theta_p = 2 * np.arccos(np.clip(np.abs(pow_matrix[0, 0]), 0, 1))
gamma_p = np.angle(pow_matrix[0, 0])
if abs(np.sin(theta_p / 2)) < 1e-12:
# Diagonal matrix: the off-diagonal entries are zero, so phi and
# lambda cannot be read from them. Only phi + lambda is fixed (by
# the entry [1, 1] = e^{i(phi+lambda+gamma)}), so pick phi = 0 and
# put the whole phase in lambda.
phi_p = 0.0
lambda_p = np.angle(pow_matrix[1, 1]) - gamma_p
else:
phi_p = np.angle(pow_matrix[1, 0] / np.sin(theta_p / 2)) - gamma_p
lambda_p = np.angle(-pow_matrix[0, 1] / np.sin(theta_p / 2)) - gamma_p
return GateU(theta_p, phi_p, lambda_p, gamma_p)
[docs]
def supports_canonical_rewrite(self):
# A primitive of the canonical gate set.
return False
def _decompose(self, circ, qubits, bits, zvars):
q = qubits[0]
circ.push(self, q)
return circ
[docs]
def convert_to_numeric(self, matrix):
"""
Convert a symbolic matrix to a numeric numpy array.
"""
return np.array(
[[complex(elem.evalf()) for elem in row] for row in matrix],
dtype=np.complex128,
)