PennyLaneAI / pennylane-rigetti

This PennyLane plugin allows the Rigetti Forest QPUs, QVM, and wavefunction simulator to optimize quantum circuits.
https://docs.pennylane.ai/projects/rigetti
BSD 3-Clause "New" or "Revised" License
37 stars 28 forks source link

Trouble compiling for QPU #123

Open genos opened 1 year ago

genos commented 1 year ago

Introduction

Sorry to trouble y'all again, but I've got another issue.

I'm trying to run a tree tensor network on Aspen-M-2, but I hit a compilation issue. Trying to sidestep the issue gets me a little further, but then quilc has trouble with the number of MEASURE calls—especially when I only need one. Any ideas or help you can provide would be greatly appreciated!

Setup

Here's the code for creating the circuit given a specific device:

Setup code ```python import pennylane as qml from pennylane import numpy as np NUM_QUBITS, PARAM_SHAPE = 8, (7, 2) def block(weights, wires): qml.RX(weights[0], wires=wires[0]) qml.RX(weights[1], wires=wires[1]) qml.CNOT(wires=wires) def make_circuit(device, wires=None): if wires is None: wires = range(NUM_QUBITS) @qml.qnode(device) def circuit(X, parameters): qml.AngleEmbedding(X, wires=wires, rotation="Z") qml.TTN( wires=wires, n_block_wires=2, block=block, n_params_block=2, template_weights=parameters, ) return qml.expval(qml.PauliZ(NUM_QUBITS - 1)) return circuit ```

This looks like it builds the circuit I want:

Circuit drawing code ```python fig, ax = qml.draw_mpl( make_circuit(qml.device("default.qubit", wires=NUM_QUBITS)), expansion_strategy="device", style="solarized_light", )(np.zeros(NUM_QUBITS), np.zeros(PARAM_SHAPE)) fig.set_size_inches(5, 4); ```

circuit

And this works fine with the default.qubit device:

circuit = make_circuit(qml.device("default.qubit", wires=NUM_QUBITS))
print(circuit(np.zeros(NUM_QUBITS), np.zeros(PARAM_SHAPE)))
# tensor(1., requires_grad=True)

First Problem

Running quilc -S and attempting to compile this for Aspen-M-2, we encounter our first problem. Attempting to build the circuit with 8 specific qubits chosen due to their connectivity on the QPU, we have:

aspen_m_2 = qml.device("rigetti.qpu", device="Aspen-M-2", active_reset=True, compiler_timeout=100)
circuit = make_circuit(aspen_m_2, wires=[113, 114, 116, 115, 100, 101, 103, 102])
circuit(np.zeros(NUM_QUBITS), np.zeros(PARAM_SHAPE))

However, this throws a WireError:

WireError from above code ```python-traceback WireError Traceback (most recent call last) File REPO/.venv/lib/python3.10/site-packages/pennylane/_device.py:385, in Device.map_wires(self, wires) 384 try: --> 385 mapped_wires = wires.map(self.wire_map) 386 except WireError as e: File REPO/.venv/lib/python3.10/site-packages/pennylane/wires.py:278, in Wires.map(self, wire_map) 277 if w not in wire_map: --> 278 raise WireError(f"No mapping for wire label {w} specified in wire map {wire_map}.") 280 new_wires = [wire_map[w] for w in self] WireError: No mapping for wire label 113 specified in wire map OrderedDict([(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12), (13, 13), (14, 14), (15, 15), (16, 16), (17, 17), (18, 18), (19, 19), (20, 20), (21, 21), (22, 22), (23, 23), (24, 24), (25, 25), (26, 26), (27, 27), (28, 28), (29, 29), (30, 30), (31, 31), (32, 32), (33, 33), (34, 34), (35, 35), (36, 36), (37, 37), (38, 38), (39, 39), (40, 40), (41, 41), (42, 42), (43, 43), (44, 44), (45, 45), (46, 46), (47, 47), (48, 48), (49, 49), (50, 50), (51, 51), (52, 52), (53, 53), (54, 54), (55, 55), (56, 56), (57, 57), (58, 58), (59, 59), (60, 60), (61, 61), (62, 62), (63, 63), (64, 64), (65, 65), (66, 66), (67, 67), (68, 68), (69, 69), (70, 70), (71, 71), (72, 72), (73, 73), (74, 74), (75, 75), (76, 76), (77, 77), (78, 78), (79, 79)]). The above exception was the direct cause of the following exception: WireError Traceback (most recent call last) Cell In[4], line 3 1 aspen_m_2 = qml.device("rigetti.qpu", device="Aspen-M-2", active_reset=True, compiler_timeout=100) 2 circuit = make_circuit(aspen_m_2, wires=[113, 114, 116, 115, 100, 101, 103, 102]) ----> 3 circuit(np.zeros(NUM_QUBITS), np.zeros(PARAM_SHAPE)) File REPO/.venv/lib/python3.10/site-packages/pennylane/qnode.py:847, in QNode.__call__(self, *args, **kwargs) 843 self._update_original_device() 845 return res --> 847 res = qml.execute( 848 [self.tape], 849 device=self.device, 850 gradient_fn=self.gradient_fn, 851 interface=self.interface, 852 gradient_kwargs=self.gradient_kwargs, 853 override_shots=override_shots, 854 **self.execute_kwargs, 855 ) 857 if old_interface == "auto": 858 self.interface = "auto" File REPO/.venv/lib/python3.10/site-packages/pennylane/interfaces/execution.py:724, in execute(tapes, device, gradient_fn, interface, mode, gradient_kwargs, cache, cachesize, max_diff, override_shots, expand_fn, max_expansion, device_batch_transform) 718 except ImportError as e: 719 raise qml.QuantumFunctionError( 720 f"{mapped_interface} not found. Please install the latest " 721 f"version of {mapped_interface} to enable the '{mapped_interface}' interface." 722 ) from e --> 724 res = _execute( 725 tapes, device, execute_fn, gradient_fn, gradient_kwargs, _n=1, max_diff=max_diff, mode=_mode 726 ) 728 return batch_fn(res) File REPO/.venv/lib/python3.10/site-packages/pennylane/interfaces/autograd.py:81, in execute(tapes, device, execute_fn, gradient_fn, gradient_kwargs, _n, max_diff, mode) 75 # pylint misidentifies autograd.builtins as a dict 76 # pylint: disable=no-member 77 parameters = autograd.builtins.tuple( 78 [autograd.builtins.list(t.get_parameters()) for t in tapes] 79 ) ---> 81 return _execute( 82 parameters, 83 tapes=tapes, 84 device=device, 85 execute_fn=execute_fn, 86 gradient_fn=gradient_fn, 87 gradient_kwargs=gradient_kwargs, 88 _n=_n, 89 max_diff=max_diff, 90 )[0] File REPO/.venv/lib/python3.10/site-packages/autograd/tracer.py:48, in primitive..f_wrapped(*args, **kwargs) 46 return new_box(ans, trace, node) 47 else: ---> 48 return f_raw(*args, **kwargs) File REPO/.venv/lib/python3.10/site-packages/pennylane/interfaces/autograd.py:125, in _execute(parameters, tapes, device, execute_fn, gradient_fn, gradient_kwargs, _n, max_diff) 104 """Autodifferentiable wrapper around ``Device.batch_execute``. 105 106 The signature of this function is designed to work around Autograd restrictions. (...) 122 understand the consequences! 123 """ 124 with qml.tape.Unwrap(*tapes): --> 125 res, jacs = execute_fn(tapes, **gradient_kwargs) 127 for i, r in enumerate(res): 128 if any(isinstance(m, CountsMP) for m in tapes[i].measurements): File REPO/.venv/lib/python3.10/site-packages/pennylane/interfaces/execution.py:206, in cache_execute..wrapper(tapes, **kwargs) 202 return (res, []) if return_tuple else res 204 else: 205 # execute all unique tapes that do not exist in the cache --> 206 res = fn(execution_tapes.values(), **kwargs) 208 final_res = [] 210 for i, tape in enumerate(tapes): File REPO/.venv/lib/python3.10/site-packages/pennylane/interfaces/execution.py:131, in cache_execute..fn(tapes, **kwargs) 129 def fn(tapes: Sequence[QuantumTape], **kwargs): # pylint: disable=function-redefined 130 tapes = [expand_fn(tape) for tape in tapes] --> 131 return original_fn(tapes, **kwargs) File ~/.pyenv/versions/3.10.7/lib/python3.10/contextlib.py:79, in ContextDecorator.__call__..inner(*args, **kwds) 76 @wraps(func) 77 def inner(*args, **kwds): 78 with self._recreate_cm(): ---> 79 return func(*args, **kwds) File REPO/.venv/lib/python3.10/site-packages/pennylane/_qubit_device.py:656, in QubitDevice.batch_execute(self, circuits) 653 self.reset() 655 # TODO: Insert control on value here --> 656 res = self.execute(circuit) 657 results.append(res) 659 if self.tracker.active: File REPO/.venv/lib/python3.10/site-packages/pennylane_rigetti/qpu.py:198, in QPUDevice.execute(self, circuit, **kwargs) 192 def execute(self, circuit: QuantumTape, **kwargs): 193 self._skip_generate_samples = ( 194 all(obs.return_type == Expectation for obs in circuit.observables) 195 and not self.parametric_compilation 196 ) --> 198 return super().execute(circuit, **kwargs) File REPO/.venv/lib/python3.10/site-packages/pennylane_rigetti/qc.py:259, in QuantumComputerDevice.execute(self, circuit, **kwargs) 257 if self.parametric_compilation: 258 self._circuit_hash = circuit.graph.hash --> 259 return super().execute(circuit, **kwargs) File REPO/.venv/lib/python3.10/site-packages/pennylane/_qubit_device.py:432, in QubitDevice.execute(self, circuit, **kwargs) 429 self.check_validity(circuit.operations, circuit.observables) 431 # apply all circuit operations --> 432 self.apply(circuit.operations, rotations=circuit.diagonalizing_gates, **kwargs) 434 # generate computational basis samples 435 if self.shots is not None or circuit.is_sampled: File REPO/.venv/lib/python3.10/site-packages/pennylane_rigetti/qc.py:189, in QuantumComputerDevice.apply(self, operations, **kwargs) 186 self.prog = prag + self.prog 188 if self.parametric_compilation: --> 189 self.prog += self.apply_parametric_operations(operations) 190 else: 191 self.prog += self.apply_circuit_operations(operations) File REPO/.venv/lib/python3.10/site-packages/pennylane_rigetti/qc.py:216, in QuantumComputerDevice.apply_parametric_operations(self, operations) 213 # Apply the circuit operations 214 for i, operation in enumerate(operations): 215 # map the operation wires to the physical device qubits --> 216 device_wires = self.map_wires(operation.wires) 218 if i > 0 and operation.name in ("QubitStateVector", "BasisState"): 219 raise DeviceError( 220 f"Operation {operation.name} cannot be used after other Operations have already been applied " 221 f"on a {self.short_name} device." 222 ) File REPO/.venv/lib/python3.10/site-packages/pennylane/_device.py:387, in Device.map_wires(self, wires) 385 mapped_wires = wires.map(self.wire_map) 386 except WireError as e: --> 387 raise WireError( 388 f"Did not find some of the wires {wires} on device with wires {self.wires}." 389 ) from e 391 return mapped_wires WireError: Did not find some of the wires on device with wires . ```

Workaround and Second Problem

We can sidestep the above WireError if we overwrite the wire map for our device—though of course, I'd prefer to not have to. However, doing so leads to a different error: quilc has trouble compiling the resulting Program, I think because there are too many MEASURE calls in it.

aspen_m_2._wire_map = {q: q for q in aspen_m_2.qc.qubits()}
circuit = make_circuit(aspen_m_2, wires=[113, 114, 116, 115, 100, 101, 103, 102])
circuit(np.zeros(NUM_QUBITS), np.zeros(PARAM_SHAPE))
QCSHTTPStatusError from the above code 3 circuit(np.zeros(NUM_QUBITS), np.zeros(PARAM_SHAPE)) File REPO/.venv/lib/python3.10/site-packages/pennylane/qnode.py:847, in QNode.__call__(self, *args, **kwargs) 843 self._update_original_device() 845 return res --> 847 res = qml.execute( 848 [self.tape], 849 device=self.device, 850 gradient_fn=self.gradient_fn, 851 interface=self.interface, 852 gradient_kwargs=self.gradient_kwargs, 853 override_shots=override_shots, 854 **self.execute_kwargs, 855 ) 857 if old_interface == "auto": 858 self.interface = "auto" File REPO/.venv/lib/python3.10/site-packages/pennylane/interfaces/execution.py:724, in execute(tapes, device, gradient_fn, interface, mode, gradient_kwargs, cache, cachesize, max_diff, override_shots, expand_fn, max_expansion, device_batch_transform) 718 except ImportError as e: 719 raise qml.QuantumFunctionError( 720 f"{mapped_interface} not found. Please install the latest " 721 f"version of {mapped_interface} to enable the '{mapped_interface}' interface." 722 ) from e --> 724 res = _execute( 725 tapes, device, execute_fn, gradient_fn, gradient_kwargs, _n=1, max_diff=max_diff, mode=_mode 726 ) 728 return batch_fn(res) File REPO/.venv/lib/python3.10/site-packages/pennylane/interfaces/autograd.py:81, in execute(tapes, device, execute_fn, gradient_fn, gradient_kwargs, _n, max_diff, mode) 75 # pylint misidentifies autograd.builtins as a dict 76 # pylint: disable=no-member 77 parameters = autograd.builtins.tuple( 78 [autograd.builtins.list(t.get_parameters()) for t in tapes] 79 ) ---> 81 return _execute( 82 parameters, 83 tapes=tapes, 84 device=device, 85 execute_fn=execute_fn, 86 gradient_fn=gradient_fn, 87 gradient_kwargs=gradient_kwargs, 88 _n=_n, 89 max_diff=max_diff, 90 )[0] File REPO/.venv/lib/python3.10/site-packages/autograd/tracer.py:48, in primitive..f_wrapped(*args, **kwargs) 46 return new_box(ans, trace, node) 47 else: ---> 48 return f_raw(*args, **kwargs) File REPO/.venv/lib/python3.10/site-packages/pennylane/interfaces/autograd.py:125, in _execute(parameters, tapes, device, execute_fn, gradient_fn, gradient_kwargs, _n, max_diff) 104 """Autodifferentiable wrapper around ``Device.batch_execute``. 105 106 The signature of this function is designed to work around Autograd restrictions. (...) 122 understand the consequences! 123 """ 124 with qml.tape.Unwrap(*tapes): --> 125 res, jacs = execute_fn(tapes, **gradient_kwargs) 127 for i, r in enumerate(res): 128 if any(isinstance(m, CountsMP) for m in tapes[i].measurements): File REPO/.venv/lib/python3.10/site-packages/pennylane/interfaces/execution.py:206, in cache_execute..wrapper(tapes, **kwargs) 202 return (res, []) if return_tuple else res 204 else: 205 # execute all unique tapes that do not exist in the cache --> 206 res = fn(execution_tapes.values(), **kwargs) 208 final_res = [] 210 for i, tape in enumerate(tapes): File REPO/.venv/lib/python3.10/site-packages/pennylane/interfaces/execution.py:131, in cache_execute..fn(tapes, **kwargs) 129 def fn(tapes: Sequence[QuantumTape], **kwargs): # pylint: disable=function-redefined 130 tapes = [expand_fn(tape) for tape in tapes] --> 131 return original_fn(tapes, **kwargs) File ~/.pyenv/versions/3.10.7/lib/python3.10/contextlib.py:79, in ContextDecorator.__call__..inner(*args, **kwds) 76 @wraps(func) 77 def inner(*args, **kwds): 78 with self._recreate_cm(): ---> 79 return func(*args, **kwds) File REPO/.venv/lib/python3.10/site-packages/pennylane/_qubit_device.py:656, in QubitDevice.batch_execute(self, circuits) 653 self.reset() 655 # TODO: Insert control on value here --> 656 res = self.execute(circuit) 657 results.append(res) 659 if self.tracker.active: File REPO/.venv/lib/python3.10/site-packages/pennylane_rigetti/qpu.py:198, in QPUDevice.execute(self, circuit, **kwargs) 192 def execute(self, circuit: QuantumTape, **kwargs): 193 self._skip_generate_samples = ( 194 all(obs.return_type == Expectation for obs in circuit.observables) 195 and not self.parametric_compilation 196 ) --> 198 return super().execute(circuit, **kwargs) File REPO/.venv/lib/python3.10/site-packages/pennylane_rigetti/qc.py:259, in QuantumComputerDevice.execute(self, circuit, **kwargs) 257 if self.parametric_compilation: 258 self._circuit_hash = circuit.graph.hash --> 259 return super().execute(circuit, **kwargs) File REPO/.venv/lib/python3.10/site-packages/pennylane/_qubit_device.py:436, in QubitDevice.execute(self, circuit, **kwargs) 434 # generate computational basis samples 435 if self.shots is not None or circuit.is_sampled: --> 436 self._samples = self.generate_samples() 438 measurements = circuit.measurements 439 counts_exist = any(isinstance(m, CountsMP) for m in measurements) File REPO/.venv/lib/python3.10/site-packages/pennylane_rigetti/qpu.py:201, in QPUDevice.generate_samples(self) 200 def generate_samples(self): --> 201 return None if self._skip_generate_samples else super().generate_samples() File REPO/.venv/lib/python3.10/site-packages/pennylane_rigetti/qc.py:271, in QuantumComputerDevice.generate_samples(self) 269 self._compiled_program = self._compiled_program_dict.get(self.circuit_hash, None) 270 if self._compiled_program is None: --> 271 self._compiled_program = self.compile() 272 self._compiled_program_dict[self.circuit_hash] = self._compiled_program 273 else: 274 # Parametric compilation is disabled, just compile the program File REPO/.venv/lib/python3.10/site-packages/pennylane_rigetti/qc.py:253, in QuantumComputerDevice.compile(self) 251 def compile(self) -> QuantumExecutable: 252 """Compiles the program for the target device""" --> 253 return self.qc.compile(self.prog) File REPO/.venv/lib/python3.10/site-packages/pyquil/api/_quantum_computer.py:403, in QuantumComputer.compile(self, program, to_native_gates, optimize, protoquil) 400 else: 401 nq_program = program --> 403 return self.compiler.native_quil_to_executable(nq_program) File REPO/.venv/lib/python3.10/site-packages/pyquil/api/_compiler.py:119, in QPUCompiler.native_quil_to_executable(self, nq_program) 111 request = TranslateNativeQuilToEncryptedBinaryRequest( 112 quil=arithmetic_response.quil, num_shots=nq_program.num_shots 113 ) 114 with self._qcs_client() as qcs_client: # type: httpx.Client 115 response = translate_native_quil_to_encrypted_binary( 116 client=qcs_client, 117 quantum_processor_id=self.quantum_processor_id, 118 json_body=request, --> 119 ).parsed 121 ro_sources = cast(List[List[str]], [] if response.ro_sources == UNSET else response.ro_sources) 123 def to_expression(rule: str) -> ExpressionDesignator: 124 # We can only parse complete lines of Quil, so we wrap the arithmetic expression 125 # in a valid Quil instruction to parse it. 126 # TODO: This hack should be replaced after #687 File REPO/.venv/lib/python3.10/site-packages/qcs_api_client/types.py:67, in Response.parsed(self) 61 """ 62 Return the response body parsed into an API model. 63 64 Value is memoized after the first successful call. 65 """ 66 if self._parsed is None: ---> 67 self._parsed = self._parse_function(response=self) 69 return self._parsed File REPO/.venv/lib/python3.10/site-packages/qcs_api_client/api/translation/translate_native_quil_to_encrypted_binary.py:39, in _parse_response(response) 38 def _parse_response(*, response: httpx.Response) -> TranslateNativeQuilToEncryptedBinaryResponse: ---> 39 raise_for_status(response) 40 if response.status_code == 200: 41 response_200 = TranslateNativeQuilToEncryptedBinaryResponse.from_dict(response.json()) File REPO/.venv/lib/python3.10/site-packages/qcs_api_client/util/errors.py:33, in raise_for_status(res) 30 except (JSONDecodeError, KeyError): 31 pass ---> 33 raise QCSHTTPStatusError(message, error=error, response=res) QCSHTTPStatusError: QCS API call POST https://api.qcs.rigetti.com/v1/quantumProcessors/Aspen-M-2:translateNativeQuilToEncryptedBinary failed with status 400: {"code":"translation_error","message":"Memory-region re-declared: q24_unclassified","requestId":""} ```

However, if we take the pyQuil program from the device and clean up unused MEASURE instructions, the resulting program compiles without issue:

p = Program()
for i in aspen_m_2.prog.instructions:
    if not i.out().startswith("MEASURE") or i.get_qubits() == {102}:
        p += i
print(p)
Program with only one MEASURE ``` PRAGMA INITIAL_REWIRING "PARTIAL" RESET RZ(0) 113 RZ(0) 114 RZ(0) 116 RZ(0) 115 RZ(0) 100 RZ(0) 101 RZ(0) 103 RZ(0) 102 RX(0) 113 RX(0) 114 CNOT 113 114 RX(0) 116 RX(0) 115 CNOT 116 115 RX(0) 100 RX(0) 101 CNOT 100 101 RX(0) 103 RX(0) 102 CNOT 103 102 RX(0) 114 RX(0) 115 CNOT 114 115 RX(0) 101 RX(0) 102 CNOT 101 102 RX(0) 115 RX(0) 102 CNOT 115 102 DECLARE ro BIT[80] MEASURE 102 ro[42] ```
print(aspen_m_2.qc.compile(p))
# EncryptedProgram(
#  program=<SNIP>,
#  memory_descriptors={'ro': ParameterSpec(length=80, type='BIT')},
#  ro_sources={<MRef q10_unclassified[0]>: 'q10_unclassified', <MRef ro[42]>: 'q10'},
#  recalculation_table={}, _memory=Memory(values={})
# )
CatalinaAlbornoz commented 1 year ago

Thank you for opening this issue @genos !