Skip to content
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

Trouble compiling for QPU #123

Open
genos opened this issue Feb 13, 2023 · 1 comment
Open

Trouble compiling for QPU #123

genos opened this issue Feb 13, 2023 · 1 comment

Comments

@genos
Copy link
Contributor

genos commented Feb 13, 2023

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);

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
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 codeQCSHTTPStatusError 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={})
# )
@CatalinaAlbornoz
Copy link

Thank you for opening this issue @genos !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants