rigetti / pyquil

A Python library for quantum programming using Quil.
http://docs.rigetti.com
Apache License 2.0
1.41k stars 343 forks source link

DefGate broken in the NumpyWavefunctionSimulator #1059

Closed josh146 closed 5 years ago

josh146 commented 5 years ago

Issue Description

DefGate seems to be broken in NumpyWavefunctionSimulator.

How to Reproduce

Code Snippet

Use any custom gate definition. Here I just copied from http://docs.rigetti.com/en/stable/basics.html#defining-parametric-gates.

from pyquil import Program
from pyquil.quilatom import Parameter, quil_sin, quil_cos 
from pyquil.quilbase import DefGate
from pyquil.gates import X, Y, Z, H, PHASE, RX, RY, RZ, CZ, SWAP, CNOT
import numpy as np 

# Define the new gate from a matrix 
theta = Parameter('theta') 
crx = np.array([ 
    [1, 0, 0, 0], 
    [0, 1, 0, 0], 
    [0, 0, quil_cos(theta / 2), -1j * quil_sin(theta / 2)], 
    [0, 0, -1j * quil_sin(theta / 2), quil_cos(theta / 2)] 
]) 

gate_definition = DefGate('CRX', crx, [theta]) 
CRX = gate_definition.get_constructor() 

# Create our program and use the new parametric gate 
p = Program() 
p += [gate_definition, CRX(np.pi/2)(0, 1)] 
p += H(0) 
p += CRX(np.pi/2)(0, 1)

from pyquil.numpy_simulator import NumpyWavefunctionSimulator
from pyquil.pyqvm import PyQVM

qc = PyQVM(n_qubits=2, quantum_simulator_type=NumpyWavefunctionSimulator)
qc.execute(p).wf_simulator.wf.flatten()

Error Output

KeyError                                  Traceback (most recent call last)
<ipython-input-14-a45963b2b08a> in <module>
----> 1 qc.execute(p).wf_simulator.wf.flatten()

~/miniconda3/envs/py36/lib/python3.6/site-packages/pyquil/pyqvm.py in execute(self, program)
    449         halted = len(program) == 0
    450         while not halted:
--> 451             halted = self.transition()
    452 
    453         return self

~/miniconda3/envs/py36/lib/python3.6/site-packages/pyquil/pyqvm.py in transition(self)
    282                                                  qubits=[q.index for q in instruction.qubits])
    283             else:
--> 284                 self.wf_simulator.do_gate(gate=instruction)
    285 
    286             for noise_type, noise_prob in self.post_gate_noise_probabilities.items():

~/miniconda3/envs/py36/lib/python3.6/site-packages/pyquil/numpy_simulator.py in do_gate(self, gate)
    266         :return: ``self`` to support method chaining.
    267         """
--> 268         gate_matrix, qubit_inds = _get_gate_tensor_and_qubits(gate=gate)
    269         # Note to developers: you can use either einsum- or tensordot- based functions.
    270         # tensordot seems a little faster, but feel free to experiment.

~/miniconda3/envs/py36/lib/python3.6/site-packages/pyquil/numpy_simulator.py in _get_gate_tensor_and_qubits(gate)
    160     """
    161     if len(gate.params) > 0:
--> 162         matrix = QUANTUM_GATES[gate.name](*gate.params)
    163     else:
    164         matrix = QUANTUM_GATES[gate.name]

KeyError: 'CRX'

Environment Context

Operating System: Ubuntu 18.04.1

Python Version (python -V): 3.6.8

Quilc Version (quilc --version): latest rigetti/qvm docker image

QVM Version (qvm --version): latest rigetti/quilc docker image

Python Environment Details (pip freeze or conda list):

pyquil==2.12.0
numpy==1.16.4
scipy==1.3.0
appleby commented 5 years ago

Hi, thanks for reporting this.

I am not super familiar with the PyQVM API, but it looks like there are several things going on.

  1. PyQVM doesn't support parametric defgates. There is code in the PyQVM class to detect parametric defgates and report a sane error, but calling execute in this way appears to circumvent it. So this is probably a bug.

  2. PyQVM implements the "Quantum Abstract Machine" (QAM) interface, and I suspect you're meant to either interact with it via the QAM interface, or else indirectly via a Quantum Computer. Neither of those classes implement an execute method, so perhaps that method should "private" to the PyQVM class. This is just a guess though.

  3. If instead of calling execute directly, you follow a procedure similar to the one outlined in the QuantumComputer docs, then you'll get an explicit NotImplementedError: PyQVM does not support parameterized DEFGATEs. For example, if you replace the final two lines of your example code with the following:

from pyquil import get_qc
qc = get_qc('2q-pyqvm')
ro = p.declare('ro', 'BIT', 1)
p += MEASURE(1, ro[0])
compiled_program = qc.compile(p)
results = qc.run(compiled_program)
  1. You might reasonably expect PyQVM to not throw an error about the parametric DEFGATE if you're passing it a compiled program that no longer contains any references to said parametric gate other than the DEFGATE form itself. But for all I know, that is expected behavior.
josh146 commented 5 years ago

If instead of calling execute directly, you follow a procedure similar to the one outlined in the QuantumComputer docs, then you'll get an explicit NotImplementedError: PyQVM does not support parameterized DEFGATEs.

Ah, that's good to know!

PyQVM doesn't support parametric defgates.

I should clarify, that the reason I noticed this was that non-parametrized DEFGATEs used to work in the PyQVM --- they work in PyQuil 2.10, for example. We rely on this feature over in https://github.com/rigetti/pennylane-forest to compute multi-qubit arbitrary expectation values.

josh146 commented 5 years ago

For example, using

theta = 0.543

U = np.array([ 
    [1, 0, 0, 0], 
    [0, 1, 0, 0], 
    [0, 0, np.cos(theta / 2), -1j * np.sin(theta / 2)], 
    [0, 0, -1j * np.sin(theta / 2), np.cos(theta / 2)] 
]) 

gate_definition = DefGate('U_test', U) 
U_test = gate_definition.get_constructor() 

I get the same error as above in the latest PyQuil PyQVM, but it works fine in 2.10.

appleby commented 5 years ago

I see. Yes, looking at the git history, the behavior of execute w.r.t. defgates did likely change recently. As a (partial) workaround, calling the PyQVM via QAM interface methods load and run should still work in the presence of non-parametric defgates, or indeed via the QuantumComputer interfaces. Note that these reset the wavefunction and memory registers each time they are called though.

Full example calling PyQVM via QAM interface:

import numpy as np

from pyquil import Program
from pyquil.quilbase import DefGate
from pyquil.numpy_simulator import NumpyWavefunctionSimulator
from pyquil.pyqvm import PyQVM

theta = 0.543

U = np.array([
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, np.cos(theta / 2), -1j * np.sin(theta / 2)],
    [0, 0, -1j * np.sin(theta / 2), np.cos(theta / 2)]
])

gate_definition = DefGate('U_test', U)
U_test = gate_definition.get_constructor()

p = Program()
p += gate_definition
p += U_test(0, 1)
ro = p.declare('ro', 'BIT', 2)
p += MEASURE(0, ro[0])
p += MEASURE(1, ro[1])

qam = PyQVM(n_qubits=2, quantum_simulator_type=NumpyWavefunctionSimulator)
qam.load(p)
qam.run()
qam.read_memory(region_name="ro")

Or via the QuantumComputer interface (building on the last example):

from pyquil import get_qc
qc = get_qc('2q-pyqvm')
p = Program()
p += gate_definition
p += U_test(0, 1)
qc.run_and_measure(p, trials=1)