Skip to content

Commit

Permalink
Merge branch 'main' into bugfix-migration-from-qasmsimulator
Browse files Browse the repository at this point in the history
  • Loading branch information
grace-harper authored Sep 19, 2023
2 parents 147bf94 + a8ada5e commit 370d63f
Show file tree
Hide file tree
Showing 21 changed files with 106 additions and 416 deletions.
3 changes: 0 additions & 3 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,6 @@ disable=raw-checker-failed,
missing-raises-doc,
logging-too-many-args,
spelling, # way too noisy
no-self-use, # disabled as it is too verbose
bad-continuation, bad-whitespace # differences of opinion with black

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
Expand Down
98 changes: 19 additions & 79 deletions docs/tutorials/QEC_Framework_IEEE_2022.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pylint==2.11.1
pylint
coverage==6.1.1
cmake!=3.17.1,!=3.17.0
qiskit-sphinx-theme>=1.6
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
qiskit-terra>=0.21.2, <0.24.0
qiskit-terra>=0.24.0
qiskit-aer>=0.11.0
pybind11<=2.9.1
PyMatching>=0.6.0,!=2.0.0
Expand Down
20 changes: 9 additions & 11 deletions src/qiskit_qec/circuits/repetition_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,18 @@
# pylint: disable=invalid-name

"""Generates circuits based on repetition codes."""
from copy import deepcopy
from typing import List, Optional, Tuple

from copy import deepcopy
import numpy as np
import rustworkx as rx

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

from qiskit_qec.circuits.code_circuit import CodeCircuit
from qiskit_qec.utils import DecodingGraphNode, DecodingGraphEdge
from qiskit_qec.utils import DecodingGraphEdge, DecodingGraphNode


def _separate_string(string):
Expand Down Expand Up @@ -310,7 +309,8 @@ def string2nodes(self, string, **kwargs):
logical = "0"

string = self._process_string(string)
separated_string = _separate_string(string) # [ <boundary>, <syn>, <syn>,...]
# [ <boundary>, <syn>, <syn>,...]
separated_string = _separate_string(string)
nodes = []

# boundary nodes
Expand Down Expand Up @@ -437,7 +437,7 @@ def check_nodes(self, nodes, ignore_extra_boundary=False, minimal=False):
node = DecodingGraphNode(is_boundary=True, qubits=qubits, index=elem)
flipped_logical_nodes.append(node)

if neutral and flipped_logical_nodes == []:
if neutral and not flipped_logical_nodes:
break

return neutral, flipped_logical_nodes, num_errors
Expand Down Expand Up @@ -1290,7 +1290,7 @@ def check_nodes(self, nodes, ignore_extra_boundary=False, minimal=False):
)
flipped_logical_nodes.append(node)

if neutral and flipped_logical_nodes == []:
if neutral and not flipped_logical_nodes:
break

else:
Expand All @@ -1307,15 +1307,13 @@ def check_nodes(self, nodes, ignore_extra_boundary=False, minimal=False):

return neutral, flipped_logical_nodes, num_errors

def is_cluster_neutral(self, atypical_nodes):
def is_cluster_neutral(self, atypical_nodes: dict):
"""
Determines whether or not the cluster is neutral, meaning that one or more
errors could have caused the set of atypical nodes (syndrome changes) passed
to the method.
Args:
atypical_nodes (dictionary in the form of the return value of string2nodes)
ignore_extra_boundary (bool): If `True`, undeeded boundary nodes are
ignored.
atypical_nodes: dictionary in the form of the return value of string2nodes
"""
neutral, logicals, _ = self.check_nodes(atypical_nodes)
return neutral and not logicals
Expand Down Expand Up @@ -1560,7 +1558,7 @@ def get_error_coords(self, counts, decoding_graph, method="spitz"):
qubit = tuple(node0.qubits + [node0.properties["link qubit"]])
time = [node0.time, node0.time + (round_length - 1) / round_length]

if time != []: # only record if not nan
if time: # only record if not nan
if (qubit, time[0], time[1]) not in error_coords:
error_coords[qubit, time[0], time[1]] = {}
error_coords[qubit, time[0], time[1]][n0, n1] = prob
Expand Down
2 changes: 1 addition & 1 deletion src/qiskit_qec/circuits/surface_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ def check_nodes(self, nodes, ignore_extra_boundary=False, minimal=False):
p = y
else:
p = x
num_errors = min(num_errors, min(p + 1, self.d - p))
num_errors = min(num_errors, p + 1, self.d - p)
flipped_logicals = {1 - int(p < (self.d - 1) / 2)}

# if unneeded logical zs are given, cluster is not neutral
Expand Down
8 changes: 5 additions & 3 deletions src/qiskit_qec/decoders/decoding_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
"""
import itertools
import logging
from typing import List, Tuple
from typing import List, Tuple, Union

import numpy as np
import rustworkx as rx

from qiskit_qec.analysis.faultenumerator import FaultEnumerator
from qiskit_qec.exceptions import QiskitQECError
from qiskit_qec.utils import DecodingGraphNode, DecodingGraphEdge
from qiskit_qec.utils import DecodingGraphEdge, DecodingGraphNode


class DecodingGraph:
Expand Down Expand Up @@ -293,12 +294,13 @@ def weight_syndrome_graph(self, counts, method: str = METHOD_SPITZ):
edge_data.weight = w
self.graph.update_edge(edge[0], edge[1], edge_data)

def make_error_graph(self, data, all_logicals=True):
def make_error_graph(self, data: Union[str, List], all_logicals=True):
"""Returns error graph.
Args:
data: Either an ouput string of the code, or a list of
nodes for the code.
all_logicals(bool): Whether to do all logicals
Returns:
The subgraph of graph which corresponds to the non-trivial
Expand Down
12 changes: 5 additions & 7 deletions src/qiskit_qec/decoders/hdrg_decoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@

"""Hard decision renormalization group decoders."""

from abc import ABC
from copy import copy, deepcopy
from dataclasses import dataclass
from typing import Dict, List, Set, Tuple
from abc import ABC
from rustworkx import connected_components, distance_matrix, PyGraph

from rustworkx import PyGraph, connected_components, distance_matrix

from qiskit_qec.circuits.repetition_code import ArcCircuit
from qiskit_qec.decoders.decoding_graph import DecodingGraph
from qiskit_qec.utils import DecodingGraphNode, DecodingGraphEdge
from qiskit_qec.utils import DecodingGraphEdge, DecodingGraphNode


class ClusteringDecoder(ABC):
Expand Down Expand Up @@ -359,16 +360,13 @@ def process(self, string: str):
clusters = self.cluster(nodes)
return self.get_corrections(string, clusters)

def cluster(self, nodes):
def cluster(self, nodes: List):
"""
Create clusters using the union-find algorithm.
Args:
nodes (List): List of non-typical nodes in the syndrome graph,
of the type produced by `string2nodes`.
standard_form (Bool): Whether to use the standard form of
the clusters for clustering decoders, or the form used internally
by the class.
Returns:
clusters (dict): Dictionary with the indices of
Expand Down
8 changes: 4 additions & 4 deletions src/qiskit_qec/linear/symplectic.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
"""Symplectic functions."""

from collections import deque
from typing import List, Any, Tuple
from typing import Union, Optional
from typing import Any, List, Optional, Tuple, Union

import numpy as np

from qiskit import QiskitError

from qiskit_qec.linear import matrix as mt


Expand Down Expand Up @@ -1888,7 +1887,8 @@ def min_generating(
Args:
matrix (Optional[np.ndarray]): Input GF(2) symplectic matrix
x, z (Optional[np.ndarray]): Input hyperbolic set - pair of GF(2) symplectic matrices
x (Optional[np.ndarray]): Input hyperbolic set - pair of GF(2) symplectic matrices
z (Optional[np.ndarray]): Input hyperbolic set - pair of GF(2) symplectic matrices
Raises:
QiskitError: An input matrix is required
Expand Down
53 changes: 2 additions & 51 deletions src/qiskit_qec/operators/base_pauli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
import numpy as np

# Must be imported as follows to avoid circular import errors
import qiskit.quantum_info.operators.symplectic.clifford
from qiskit import QiskitError
from qiskit.circuit import QuantumCircuit
from qiskit.circuit.barrier import Barrier
from qiskit.circuit.delay import Delay
from qiskit.circuit.instruction import Instruction
from qiskit.quantum_info.operators.base_operator import BaseOperator
from qiskit.quantum_info.operators.mixins import AdjointMixin, MultiplyMixin

from qiskit_qec.linear import matrix as mt
from qiskit_qec.linear.symplectic import symplectic_product
from qiskit_qec.utils import pauli_rep
Expand Down Expand Up @@ -692,60 +692,11 @@ def evolve(self, other: "BasePauli", qargs: Union[None, List, int] = None, frame
ret = ret.compose(other, front=True, qargs=qargs)
return ret

# Convert Clifford to quantum circuits
if isinstance(other, qiskit.quantum_info.operators.symplectic.clifford.Clifford):
return self._evolve_clifford(other, qargs=qargs, frame=frame)

# Otherwise evolve by the inverse circuit to compute C^dg.P.C
# Evolve by the inverse circuit to compute C^dg.P.C
if frame == "s":
return self.copy()._append_circuit(other, qargs=qargs)
return self.copy()._append_circuit(other.inverse(), qargs=qargs)

def _evolve_clifford(
self,
other: qiskit.quantum_info.operators.symplectic.clifford.Clifford,
qargs: Union[None, List, int] = None,
frame: str = "h",
):
"""Heisenberg picture evolution of a Pauli by a Clifford."""
if qargs is None:
idx = slice(None)
num_act = self.num_qubits
else:
idx = list(qargs)
num_act = len(idx)

# Set return to I on qargs
ret = self.copy()
ret._x[:, idx] = False
ret._z[:, idx] = False

# pylint: disable=cyclic-import
from qiskit_qec.operators.pauli import Pauli
from qiskit_qec.operators.pauli_list import PauliList

# Get action of Pauli's from Clifford
if frame == "s":
adj = other.copy()
else:
adj = other.adjoint()
pauli_list = []
for z in self._z[:, idx]:
pauli = Pauli("I" * num_act)
for row in adj.stabilizer[z]:
pauli.compose(Pauli((row.Z[0], row.X[0], 2 * row.phase[0])), inplace=True)
pauli_list.append(pauli)
ret.dot(PauliList(pauli_list), qargs=qargs, inplace=True)

pauli_list = []
for x in self._x[:, idx]:
pauli = Pauli("I" * num_act)
for row in adj.destabilizer[x]:
pauli.compose(Pauli((row.Z[0], row.X[0], 2 * row.phase[0])), inplace=True)
pauli_list.append(pauli)
ret.dot(PauliList(pauli_list), qargs=qargs, inplace=True)
return ret

# ---------------------------------------------------------------------
# Helper Metods
# ---------------------------------------------------------------------
Expand Down
10 changes: 5 additions & 5 deletions src/qiskit_qec/operators/pauli.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from qiskit.quantum_info.operators.mixins import generate_apidocs
from qiskit.quantum_info.operators.scalar_op import ScalarOp
from qiskit.utils.deprecation import deprecate_function

from qiskit_qec.operators.base_pauli import BasePauli
from qiskit_qec.utils import pauli_rep

Expand Down Expand Up @@ -520,11 +521,11 @@ def to_instruction(self):
gate = {"I": IGate(), "X": XGate(), "Y": YGate(), "Z": ZGate()}[pauli]
else:
gate = PauliGate(pauli)
if not phase_exp:
if not phase_exp[0]:
return gate
# Add global phase
circuit = QuantumCircuit(self.num_qubits, name=str(self))
circuit.global_phase = -phase_exp * pi / 2
circuit.global_phase = -phase_exp[0] * pi / 2
circuit.append(gate, range(self.num_qubits))
return circuit.to_instruction()

Expand Down Expand Up @@ -674,9 +675,8 @@ def evolve(self, other, qargs=None, frame="h"):
"""
if qargs is None:
qargs = getattr(other, "qargs", None)
from qiskit.quantum_info.operators.symplectic.clifford import Clifford

if not isinstance(other, (Pauli, Instruction, QuantumCircuit, Clifford)):
if not isinstance(other, (Pauli, Instruction, QuantumCircuit)):
# Convert to a Pauli
other = Pauli(other)

Expand Down Expand Up @@ -712,7 +712,7 @@ def instrs2symplectic(instr: Union[Instruction, QuantumCircuit]):
if not isinstance(dinstr, (Barrier, Delay)):
next_instr = BasePauli(*Pauli.instrs2symplectic(dinstr))
if next_instr is not None:
qargs = [tup.index for tup in qregs]
qargs = [instr.find_bit(tup)[0] for tup in qregs]
ret = ret.compose(next_instr, qargs=qargs)
return ret.matrix, ret._phase_exp

Expand Down
23 changes: 6 additions & 17 deletions src/qiskit_qec/operators/pauli_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
from qiskit.exceptions import QiskitError
from qiskit.quantum_info.operators.custom_iterator import CustomIterator
from qiskit.quantum_info.operators.mixins import GroupMixin, LinearMixin
from qiskit.quantum_info.operators.symplectic.pauli_table import PauliTable
from qiskit.quantum_info.operators.symplectic.stabilizer_table import StabilizerTable

from qiskit_qec.operators.base_pauli import BasePauli
from qiskit_qec.operators.pauli import Pauli
from qiskit_qec.utils import pauli_rep
Expand All @@ -35,9 +34,7 @@ class PauliList(BasePauli, LinearMixin, GroupMixin):

def __init__(
self,
data: Union[
BasePauli, StabilizerTable, PauliTable, np.ndarray, Tuple[np.ndarray], Iterable, None
] = None,
data: Union[BasePauli, np.ndarray, Tuple[np.ndarray], Iterable, None] = None,
phase_exp: Union[int, np.ndarray, None] = None,
*,
input_pauli_encoding: str = BasePauli.EXTERNAL_PAULI_ENCODING,
Expand All @@ -60,14 +57,7 @@ def __init__(
elif isinstance(data, BasePauli):
matrix = data.matrix
phase_exp = data._phase_exp
elif isinstance(data, StabilizerTable):
# Conversion from legacy StabilizerTable
matrix = data._array
phase_exp = 2 * data.phase
elif isinstance(data, PauliTable):
# Conversion from legacy PauliTable
matrix = data._array
phase_exp = np.zeros(shape=(matrix.shape[0],), dtype=np.int8)

elif isinstance(data, np.ndarray):
if data.size == 0:
matrix = np.empty(shape=(0, 0), dtype=np.bool_)
Expand Down Expand Up @@ -160,7 +150,7 @@ def _from_paulis(data: Iterable, input_qubit_order: str = "right-to-left"):
matrix[index][num_qubits : num_qubits + pauli.num_qubits] = pauli.matrix[0][
pauli.num_qubits :
]
phase_exp[index] = pauli._phase_exp
phase_exp[index] = pauli._phase_exp[0]
return matrix, phase_exp

# ---------------------------------------------------------------------
Expand Down Expand Up @@ -289,7 +279,7 @@ def __setitem__(self, index, value):

if not isinstance(index, tuple):
# Row-only indexing
self._phase_exp[index] = value._phase_exp
self._phase_exp[index] = value._phase_exp[0]
else:
# Row and Qubit indexing
self._phase_exp[index[0]] += value._phase_exp
Expand Down Expand Up @@ -919,12 +909,11 @@ def evolve(self, other, qargs=None, frame="h"):
QiskitError: if the Clifford number of qubits and qargs don't match.
"""
from qiskit.circuit import Instruction, QuantumCircuit
from qiskit.quantum_info.operators.symplectic.clifford import Clifford

if qargs is None:
qargs = getattr(other, "qargs", None)

if not isinstance(other, (BasePauli, Instruction, QuantumCircuit, Clifford)):
if not isinstance(other, (BasePauli, Instruction, QuantumCircuit)):
# Convert to a PauliList
other = PauliList(other)

Expand Down
Loading

0 comments on commit 370d63f

Please sign in to comment.