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

Qiskit 1.0 #421

Merged
merged 11 commits into from
Jul 2, 2024
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
qiskit>=0.43,<1
qiskit
qiskit-aer>=0.11.0
qiskit_ibm_provider>=0.5.0
pybind11<=2.9.1
PyMatching>=0.6.0,!=2.0.0
rustworkx>=0.12.0
networkx>=2.6.3
sympy>=1.9
testtools
numpy>=1.21.0
ipython
ipywidgets>=8.0.5
Expand Down
1 change: 1 addition & 0 deletions src/qiskit_qec/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
linear,
operators,
structures,
test,
utils,
)

Expand Down
65 changes: 8 additions & 57 deletions src/qiskit_qec/circuits/repetition_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@
import networkx as nx

from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, transpile
from qiskit.circuit.library import XGate, RZGate
from qiskit.transpiler import PassManager, InstructionDurations
from qiskit_ibm_provider.transpiler.passes.scheduling import DynamicCircuitInstructionDurations
from qiskit_ibm_provider.transpiler.passes.scheduling import PadDynamicalDecoupling
from qiskit_ibm_provider.transpiler.passes.scheduling import ALAPScheduleAnalysis

from qiskit_qec.circuits.code_circuit import CodeCircuit
from qiskit_qec.utils import DecodingGraphEdge, DecodingGraphNode
Expand Down Expand Up @@ -979,6 +974,7 @@ def _process_string(self, string):
q_l = self.num_qubits[1] - 1 - j
qubit_l = self.links[q_l][1]
# the first results are themselves the changes
change = None
if t == 0:
change = syndrome_list[-1][j] != "0"
# if the link was involved in a just finished 202...
Expand Down Expand Up @@ -1222,19 +1218,16 @@ def is_cluster_neutral(self, nodes: dict):
self._linear,
)

def transpile(self, backend, echo=("X", "X"), echo_num=(2, 0)):
def transpile(self, backend, scheduling_method="alap"):
"""
Args:
backend (qiskit.providers.ibmq.IBMQBackend): Backend to transpile and schedule the
circuits for. The numbering of the qubits in this backend should correspond to
the numbering used in `self.links`.
echo (tuple): List of gate sequences (expressed as strings) to be used on code qubits and
link qubits, respectively. Valid strings are `'X'` and `'XZX'`.
echo_num (tuple): Number of times to repeat the sequences for code qubits and
link qubits, respectively.
scheduling_method (str): Name of scheduling pass. Arguemnt passed to `qiskit.transpile`.
Returns:
transpiled_circuit: As `self.circuit`, but with the circuits scheduled, transpiled and
with dynamical decoupling added.
transpiled_circuit: As `self.circuit`, but with the circuits scheduled and remapped
to the device connectivity.
"""

bases = list(self.circuit.keys())
Expand All @@ -1248,51 +1241,9 @@ def transpile(self, backend, echo=("X", "X"), echo_num=(2, 0)):
]

# transpile to backend
circuits = transpile(circuits, backend, initial_layout=initial_layout)

# then dynamical decoupling if needed
if any(echo_num):
if self.run_202:
durations = DynamicCircuitInstructionDurations().from_backend(backend)
else:
durations = InstructionDurations().from_backend(backend)

# set up the dd sequences
dd_sequences = []
spacings = []
for j in range(2):
if echo[j] == "X":
dd_sequences.append([XGate()] * echo_num[j])
spacings.append(None)
elif echo[j] == "XZX":
dd_sequences.append([XGate(), RZGate(np.pi), XGate()] * echo_num[j])
d = 1.0 / (2 * echo_num[j] - 1 + 1)
spacing = [d / 2] + ([0, d, d] * echo_num[j])[:-1] + [d / 2]
for _ in range(2):
spacing[0] += 1 - sum(spacing)
spacings.append(spacing)
else:
dd_sequences.append(None)
spacings.append(None)

# add in the dd sequences
for j, dd_sequence in enumerate(dd_sequences):
if dd_sequence:
if echo_num[j]:
qubits = self.qubits[j]
else:
qubits = None
pm = PassManager(
[
ALAPScheduleAnalysis(durations),
PadDynamicalDecoupling(
durations, dd_sequence, qubits=qubits, spacings=spacings[j]
),
]
)
circuits = pm.run(circuits)
if not isinstance(circuits, list):
circuits = [circuits]
circuits = transpile(
circuits, backend, initial_layout=initial_layout, scheduling_method=scheduling_method
)

return {basis: circuits[j] for j, basis in enumerate(bases)}

Expand Down
27 changes: 27 additions & 0 deletions src/qiskit_qec/test/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2018.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Functionality and helpers for testing Qiskit."""

import warnings
from .base import QiskitTestCase
from .decorators import requires_aer_provider, online_test, slow_test
from .reference_circuits import ReferenceCircuits
from .utils import Path

warnings.warn(
"The `qiskit.test` module is deprecated since Qiskit 0.46, and will be removed in Qiskit 1.0."
" This module was internal to Qiskit's test suite; if you absolutely require any of its"
" functionality, consider vendoring the code into your own project.",
DeprecationWarning,
stacklevel=2,
)
125 changes: 125 additions & 0 deletions src/qiskit_qec/test/_canonical.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Utility methods for canonicalising various Qiskit objects, to help with testing."""

import threading

from qiskit.circuit import (
BreakLoopOp,
CircuitInstruction,
ContinueLoopOp,
ControlFlowOp,
ForLoopOp,
Parameter,
QuantumCircuit,
)


class _CanonicalParametersIterator:
"""An object that, when iterated through, will produce the same sequence of parameters as every
other instance of this iterator."""

__parameters = []
__mutex = threading.Lock()

def __init__(self):
self._counter = 0

def __iter__(self):
return self

def __next__(self):
with self.__mutex:
if len(self.__parameters) >= self._counter:
param = Parameter(f"_canonicalization_loop_{self._counter}")
self.__parameters.append(param)
out = self.__parameters[self._counter]
self._counter += 1
return out


def canonicalize_control_flow(circuit: QuantumCircuit) -> QuantumCircuit:
"""Canonicalize all control-flow operations in a circuit.

This is not an efficient operation, and does not affect any properties of the circuit. Its
intent is to normalise parts of circuits that have a non-deterministic construction. These are
the ordering of bit arguments in control-flow blocks output by the builder interface, and
automatically generated ``for``-loop variables.

The canonical form sorts the bits in the arguments of these operations so that they always
appear in the order they were originally added to the outer-most circuit. For-loop variables
are re-bound into new, cached auto-generated ones."""
params = iter(_CanonicalParametersIterator())
base_bit_order = {bit: i for i, bit in enumerate(circuit.qubits)}
base_bit_order.update((bit, i) for i, bit in enumerate(circuit.clbits))

def worker(circuit, bit_map=None):
if bit_map is None:
bit_map = {bit: bit for bits in (circuit.qubits, circuit.clbits) for bit in bits}

def bit_key(bit):
return base_bit_order[bit_map[bit]]

# This isn't quite QuantumCircuit.copy_empty_like because of the bit reordering.
out = QuantumCircuit(
sorted(circuit.qubits, key=bit_key),
sorted(circuit.clbits, key=bit_key),
*circuit.qregs,
*circuit.cregs,
name=circuit.name,
global_phase=circuit.global_phase,
metadata=circuit.metadata,
)
for instruction in circuit.data:
new_instruction = instruction
# Control-flow operations associated bits in the instruction arguments with bits in the
# circuit blocks just by sequencing. All blocks must have the same width.
if isinstance(new_instruction.operation, ControlFlowOp):
op = new_instruction.operation
first_block = op.blocks[0]
inner_bit_map = dict(
zip(first_block.qubits, (bit_map[bit] for bit in new_instruction.qubits))
)
inner_bit_map.update(
zip(first_block.clbits, (bit_map[bit] for bit in new_instruction.clbits))
)
new_instruction = CircuitInstruction(
operation=op.replace_blocks(
[worker(block, inner_bit_map) for block in op.blocks]
),
qubits=sorted(new_instruction.qubits, key=bit_key),
clbits=sorted(new_instruction.clbits, key=bit_key),
)
elif isinstance(new_instruction.operation, (BreakLoopOp, ContinueLoopOp)):
new_instruction = new_instruction.replace(
qubits=sorted(new_instruction.qubits, key=bit_key),
clbits=sorted(new_instruction.clbits, key=bit_key),
)
# For for loops specifically, the control-flow builders generate a loop parameter if one
# is needed but not explicitly supplied. We want the parameters to compare equal, so we
# replace them with those from a shared list.
if isinstance(new_instruction.operation, ForLoopOp):
old_op = new_instruction.operation
indexset, loop_param, body = old_op.params
if loop_param is not None:
new_loop_param = next(params)
new_op = ForLoopOp(
indexset,
new_loop_param,
body.assign_parameters({loop_param: new_loop_param}),
)
new_instruction = new_instruction.replace(operation=new_op)
out._append(new_instruction)
return out

return worker(circuit)
Loading
Loading