pennylane-rigetti
pennylane-rigetti copied to clipboard
Trouble compiling for QPU
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
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
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);
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
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.<locals>.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.<locals>.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.<locals>.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__.<locals>.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 <Wires = [113]> on device with wires <Wires = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79]>.
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
QCSHTTPStatusError Traceback (most recent call last)
Cell In[10], line 3
1 aspen_m_2._wire_map = {q: q for q in aspen_m_2.qc.qubits()}
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.<locals>.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.<locals>.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.<locals>.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__.<locals>.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":"<SNIP>"}
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={})
# )
Thank you for opening this issue @genos !