-
Notifications
You must be signed in to change notification settings - Fork 278
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion: support __pow__ for rotation gates. #32
Comments
I agree that there are benefits, but there is no pi in the definition of Rz (I'd expect its argument to be theta in exp(+/- i*theta/2)). |
You definitely wouldn't want to remove Rz (since people will indeed want to use it). But the internal constructions can prefer ZPow over Rz and thereby gain free precision. There'd be other things to do, like ensure they interop when merging etc. |
Having rotation gates support |
Some followup on what this would look like: def _exp_i_pi(exponent):
exponent %= 2
exponent = float(exponent)
# At half-steps, give results without floating point error.
if exponent % 0.5 == 0:
return 1j**int(exponent * 2)
return cmath.exp(np.pi * 1j * exponent)
class VectorPhaseGate(BasicGate):
def __init__(self, vector_name, vector, phase_exponent=1):
"""
:param vector_name: The root name used when describing the gate.
:param vector: The eigenvector to phase. All vectors perpendicular
to this one are not affected by the operation.
Doesn't need to be normalized.
:param phase_exponent: The eigenvector is phased by -1 raised to this
power, defined as (-1)^x = exp(i π x).
"""
super(VectorPhaseGate, self).__init__()
self.vector = vector
self.vector_name = vector_name
# Wrap into (-1, +1].
self.phase_exponent = -((1-phase_exponent) % 2 - 1)
def __str__(self):
g = self.vector_name
e = self.phase_exponent
if e == 1:
return g
return g + '^' + str(e)
def tex_str(self):
g = self.vector_name
e = self.phase_exponent
if e == 1:
return g
return g + '^{' + str(e) + '}'
@property
def matrix(self):
d = len(self.vector)
m = np.dot(self.vector, self.vector.T)
p = _exp_i_pi(self.phase_exponent)
return np.identity(d) + (p - 1) * m / m.trace()
def __and__(self, other):
return OperationWithControl(self, other)
def __eq__(self, other):
return (isinstance(other, VectorPhaseGate) and
np.array_equal(self.vector, other.vector) and
self.vector_name == other.vector_name and
self.phase_exponent % 2 == other.phase_exponent % 2)
def same_axis_as(self, other):
return (isinstance(other, VectorPhaseGate) and
np.array_equal(self.vector, other.vector) and
self.vector_name == other.vector_name)
def __pow__(self, power):
return VectorPhaseGate(self.vector_name,
self.vector,
self.phase_exponent * power)
def get_inverse(self):
return VectorPhaseGate(self.vector_name,
self.vector,
-self.phase_exponent)
def get_merged(self, other):
if not self.same_axis_as(other):
raise NotMergeable('Different axis.')
return VectorPhaseGate(self.vector_name,
self.vector,
self.phase_exponent + other.phase_exponent)
X = VectorPhaseGate('X', np.mat([[1], [-1]]))
Y = VectorPhaseGate('Y', np.mat([[1], [1j]]))
Z = VectorPhaseGate('Z', np.mat([[0], [1]]))
H = VectorPhaseGate('H', np.mat([[1 - np.sqrt(2)], [1]]))
S = Z**0.5
Sdag = Z**-0.5
T = Z**0.25
Tdag = Z**-0.25 |
I think adding |
Because
pi
is transcendental, involving it in the definition of gates forces floating point error even when performing rational fractions of a whole turn.So, instead of a transcendental angle, I suggest we use a fractional exponent.
Something like this:
Note that passing in a
Fraction
instead of afloat
will work fine.A secondary benefit of this change is that the names of gates become easier to read. I tweaked the Shor example to use
ZPow(Fraction(1, 1 << (k - i)))
instead ofR(-math.pi/(1 << (k - i)))
, and this is the resource count:Which is a lot clearer than this:
(It also makes much nicer latex circuit output.)
The text was updated successfully, but these errors were encountered: