Skip to content

Commit

Permalink
Use native Gate.dagger() in Program.dagger() (#887)
Browse files Browse the repository at this point in the history
This removes a bunch of logic around creating inverse gates from
user-defined gates. If anyone thinks that stuff is useful, then I
would argue that it should be in some util function, not in
Program.dagger().
  • Loading branch information
notmgsk authored and ecpeterson committed Jun 24, 2019
1 parent 290a57f commit 4fcb9c0
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 100 deletions.
49 changes: 11 additions & 38 deletions pyquil/quil.py
Expand Up @@ -557,50 +557,23 @@ def pop(self):

def dagger(self, inv_dict=None, suffix="-INV"):
"""
Creates the conjugate transpose of the Quil program. The program must not
contain any irreversible actions (measurement, control flow, qubit allocation).
Creates the conjugate transpose of the Quil program. The program must
contain only gate applications.
Note: the keyword arguments inv_dict and suffix are kept only
for backwards compatibility and have no effect.
:return: The Quil program's inverse
:rtype: Program
"""
if not self.is_protoquil():
raise ValueError("Program must be valid Protoquil")

daggered = Program()

for gate in self._defined_gates:
if inv_dict is None or gate.name not in inv_dict:
if gate.parameters:
raise TypeError("Cannot auto define daggered version of parameterized gates")
daggered.defgate(gate.name + suffix, gate.matrix.T.conj())

for gate in reversed(self._instructions):
reversed_gate = None
qubits = [unpack_qubit(q) for q in gate.qubits]
if gate.name in QUANTUM_GATES:
if gate.name == "S":
reversed_gate = Gate("PHASE", [-pi / 2], qubits)
elif gate.name == "T":
reversed_gate = Gate("RZ", [pi / 4], qubits)
elif gate.name == "ISWAP":
reversed_gate = Gate("PSWAP", [pi / 2], qubits)
else:
negated_params = list(map(lambda x: -1 * x, gate.params))
reversed_gate = Gate(gate.name, negated_params, qubits)
reversed_gate.modifiers = gate.modifiers
daggered.inst(reversed_gate)
else:
if inv_dict is None or gate.name not in inv_dict:
gate_inv_name = gate.name + suffix
else:
gate_inv_name = inv_dict[gate.name]

reversed_gate = Gate(gate_inv_name, gate.params, qubits)
reversed_gate.modifiers = gate.modifiers
daggered.inst(reversed_gate)
if any(not isinstance(instr, Gate) for instr in self._instructions):
raise ValueError("Program to be daggered must contain only gate applications")

return daggered
# This is a bit hacky. Gate.dagger() mutates the gate object,
# rather than returning a fresh (and daggered) copy.
return Program([instr.dagger() for instr
in reversed(Program(self.out())._instructions)])

def _synthesize(self):
"""
Expand Down
72 changes: 10 additions & 62 deletions pyquil/tests/test_quil.py
Expand Up @@ -287,61 +287,13 @@ def test_measure_all():


def test_dagger():
# these gates are their own inverses
p = Program().inst(I(0), X(0), Y(0), Z(0),
H(0), CNOT(0, 1), CCNOT(0, 1, 2),
SWAP(0, 1), CSWAP(0, 1, 2))
assert p.dagger().out() == 'CSWAP 0 1 2\nSWAP 0 1\n' \
'CCNOT 0 1 2\nCNOT 0 1\nH 0\n' \
'Z 0\nY 0\nX 0\nI 0\n'

# these gates require negating a parameter
p = Program().inst(PHASE(pi, 0), RX(pi, 0), RY(pi, 0),
RZ(pi, 0), CPHASE(pi, 0, 1),
CPHASE00(pi, 0, 1), CPHASE01(pi, 0, 1),
CPHASE10(pi, 0, 1), PSWAP(pi, 0, 1))
assert p.dagger().out() == 'PSWAP(-pi) 0 1\n' \
'CPHASE10(-pi) 0 1\n' \
'CPHASE01(-pi) 0 1\n' \
'CPHASE00(-pi) 0 1\n' \
'CPHASE(-pi) 0 1\n' \
'RZ(-pi) 0\n' \
'RY(-pi) 0\n' \
'RX(-pi) 0\n' \
'PHASE(-pi) 0\n'

# these gates are special cases
p = Program().inst(S(0), T(0), ISWAP(0, 1))
assert p.dagger().out() == 'PSWAP(pi/2) 0 1\n' \
'RZ(pi/4) 0\n' \
'PHASE(-pi/2) 0\n'

# must invert defined gates
G = np.array([[0, 1], [0 + 1j, 0]])
p = Program().defgate("G", G).inst(("G", 0))
assert p.dagger().out() == 'DEFGATE G-INV:\n' \
' 0.0, -i\n' \
' 1.0, 0.0\n\n' \
'G-INV 0\n'

# can also pass in a list of inverses
inv_dict = {"G": "J"}
p = Program().defgate("G", G).inst(("G", 0))
assert p.dagger(inv_dict=inv_dict).out() == 'J 0\n'

# defined parameterized gates cannot auto generate daggered version https://github.com/rigetti/pyquil/issues/304
theta = Parameter('theta')
gparam_matrix = np.array([[quil_cos(theta / 2), -1j * quil_sin(theta / 2)],
[-1j * quil_sin(theta / 2), quil_cos(theta / 2)]])
g_param_def = DefGate('GPARAM', gparam_matrix, [theta])
p = Program(g_param_def)
with pytest.raises(TypeError):
p.dagger()
p = Program(X(0), H(0))
assert p.dagger().out() == 'DAGGER H 0\n' \
'DAGGER X 0\n'

# defined parameterized gates should passback parameters https://github.com/rigetti/pyquil/issues/304
GPARAM = g_param_def.get_constructor()
p = Program(GPARAM(pi)(1, 2))
assert p.dagger().out() == 'GPARAM-INV(pi) 1 2\n'
p = Program(X(0), MEASURE(0, 0))
with pytest.raises(ValueError) as e:
p.dagger().out()

# ensure that modifiers are preserved https://github.com/rigetti/pyquil/pull/914
p = Program()
Expand All @@ -356,14 +308,10 @@ def test_dagger():
p += T(target).controlled(control)
p += PHASE(pi, target).controlled(control)
p += CNOT(cnot_control, target).controlled(control)
assert p.dagger().out() == 'CONTROLLED CNOT 0 2 1\n' \
'CONTROLLED PHASE(-pi) 0 1\n' \
'CONTROLLED RZ(pi/4) 0 1\n' \
'CONTROLLED PHASE(-pi/2) 0 1\n' \
'CONTROLLED H 0 1\n' \
'CONTROLLED Z 0 1\n' \
'CONTROLLED Y 0 1\n' \
'CONTROLLED X 0 1\n'

for instr, instr_dagger in zip(reversed(p._instructions),
p.dagger()._instructions):
assert 'DAGGER ' + instr.out() == instr_dagger.out()


def test_construction_syntax():
Expand Down

0 comments on commit 4fcb9c0

Please sign in to comment.