Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…nitting-toolbox into LO-gate-cuts-optimizer
  • Loading branch information
ibrahim-shehzad committed Jan 4, 2024
2 parents fdb6ca8 + a666e69 commit 29732fc
Show file tree
Hide file tree
Showing 61 changed files with 706 additions and 575 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/citation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
fi
- name: Upload PDF
if: always()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: preview.pdf
path: preview.pdf
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install tox
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: '3.9'
- name: Install dependencies
Expand All @@ -39,12 +39,12 @@ jobs:
tox -edocs
- name: Upload docs artifact
if: always()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: html_docs
path: ./docs/_build/html
- name: Deploy docs
if: ${{ github.ref == 'refs/heads/stable/0.4' }}
if: ${{ github.ref == 'refs/heads/stable/0.5' }}
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
with:
fetch-depth: 0
- name: Set up Python 3.9
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.9'
- name: Install tox
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/test_development_versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies (development versions)
Expand All @@ -32,11 +32,12 @@ jobs:
python -m pip install --upgrade pip tox
python -m pip install toml typer
python tools/extremal-python-dependencies.py add-dependency \
"qiskit-terra @ git+https://github.com/Qiskit/qiskit.git" \
"qiskit-terra @ git+https://github.com/Qiskit/qiskit.git@stable/0.46" \
--inplace
python tools/extremal-python-dependencies.py pin-dependencies \
"qiskit @ git+https://github.com/Qiskit/qiskit.git#subdirectory=qiskit_pkg" \
"qiskit @ git+https://github.com/Qiskit/qiskit.git@stable/0.46#subdirectory=qiskit_pkg" \
"qiskit-ibm-runtime @ git+https://github.com/Qiskit/qiskit-ibm-runtime.git" \
"qiskit-algorithms @ git+https://github.com/qiskit-community/qiskit-algorithms.git" \
--inplace
- name: Modify tox.ini for more thorough check
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_latest_versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_minimum_versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies (minimum versions)
Expand Down
2 changes: 1 addition & 1 deletion .mergify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ pull_request_rules:
actions:
backport:
branches:
- stable/0.4
- stable/0.5
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
[![Release](https://img.shields.io/pypi/v/circuit-knitting-toolbox.svg?label=Release)](https://github.com/Qiskit-Extensions/circuit-knitting-toolbox/releases)
![Platform](https://img.shields.io/badge/%F0%9F%92%BB%20Platform-Linux%20%7C%20macOS%20%7C%20Windows-informational)
[![Python](https://img.shields.io/pypi/pyversions/circuit-knitting-toolbox?label=Python&logo=python)](https://www.python.org/)
[![Qiskit](https://img.shields.io/badge/Qiskit-%E2%89%A5%200.43.0-6133BD?logo=qiskit)](https://github.com/Qiskit/qiskit)
[![Qiskit Nature](https://img.shields.io/badge/Qiskit%20Nature-%E2%89%A5%200.6.0-6133BD?logo=qiskit)](https://github.com/Qiskit/qiskit-nature)
[![Qiskit](https://img.shields.io/badge/Qiskit-%E2%89%A5%200.44.1%2C%20%3C1.0-6133BD?logo=qiskit)](https://github.com/Qiskit/qiskit)
[![Qiskit Nature](https://img.shields.io/badge/Qiskit%20Nature-%E2%89%A5%200.6.0%2C%20%3C0.7-6133BD?logo=qiskit)](https://github.com/Qiskit/qiskit-nature)
<br />
[![Docs (stable)](https://img.shields.io/badge/%F0%9F%93%84%20Docs-stable-blue.svg)](https://qiskit-extensions.github.io/circuit-knitting-toolbox/)
[![DOI](https://zenodo.org/badge/543181258.svg)](https://zenodo.org/badge/latestdoi/543181258)
Expand Down
11 changes: 4 additions & 7 deletions circuit_knitting/cutting/cutqc/wire_cutting.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,6 @@ def find_wire_cuts(
subcircuits: Sequence[Any] = cut_solution["subcircuits"]
num_cuts: int = cut_solution["num_cuts"]
_print_cutter_result(
num_subcircuit=len(subcircuits),
num_cuts=num_cuts,
subcircuits=subcircuits,
counter=counter,
Expand Down Expand Up @@ -558,7 +557,6 @@ def cut_circuit_wire(
if verbose:
print("-" * 20)
_print_cutter_result(
num_subcircuit=len(cut_solution["subcircuits"]),
num_cuts=cut_solution["num_cuts"],
subcircuits=cut_solution["subcircuits"],
counter=cut_solution["counter"],
Expand All @@ -569,7 +567,6 @@ def cut_circuit_wire(


def _print_cutter_result(
num_subcircuit: int,
num_cuts: int,
subcircuits: Sequence[QuantumCircuit],
counter: dict[int, dict[str, int]],
Expand All @@ -579,7 +576,6 @@ def _print_cutter_result(
Pretty print the results.
Args:
num_subciruit: The number of subcircuits
num_cuts: The number of cuts
subcircuits: The list of subcircuits
counter: The dictionary containing all meta information regarding
Expand All @@ -589,7 +585,8 @@ def _print_cutter_result(
Returns:
None
"""
for subcircuit_idx in range(num_subcircuit):
print(f"num_cuts = {num_cuts}")
for subcircuit_idx, subcircuit in enumerate(subcircuits):
print("subcircuit %d" % subcircuit_idx)
print(
"\u03C1 qubits = %d, O qubits = %d, width = %d, effective = %d, depth = %d, size = %d"
Expand All @@ -602,7 +599,7 @@ def _print_cutter_result(
counter[subcircuit_idx]["size"],
)
)
print(subcircuits[subcircuit_idx])
print(subcircuit)
print("Estimated cost = %.3e" % classical_cost, flush=True)


Expand All @@ -621,7 +618,7 @@ def _cuts_parser(
that are affected by these cuts
"""
dag = circuit_to_dag(circ)
positions = []
positions: list[tuple[Qubit, int]] = []
for position in cuts:
if len(position) != 2:
raise ValueError(
Expand Down
10 changes: 3 additions & 7 deletions circuit_knitting/cutting/cutting_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,15 @@ def execute_experiments(
assert isinstance(samplers, dict)
samplers_dict = samplers

# Make sure the first two cregs in each circuit are for QPD and observable measurements
# Make sure the first two cregs in each circuit are for observable and QPD measurements, respectively.
# Run a job for each partition and collect results
results = {}
for label in subexperiments_dict.keys():
for circ in subexperiments_dict[label]:
if (
len(circ.cregs) != 2
or circ.cregs[1].name != "observable_measurements"
or circ.cregs[0].name != "qpd_measurements"
or circ.cregs[0].name != "observable_measurements"
or circ.cregs[1].name != "qpd_measurements"
or sum([reg.size for reg in circ.cregs]) != circ.num_clbits
):
# If the classical bits/registers are in any other format than expected, the user must have
Expand All @@ -150,10 +150,6 @@ def execute_experiments(
)
results[label] = samplers_dict[label].run(subexperiments_dict[label]).result()

for label, result in results.items():
for i, metadata in enumerate(result.metadata):
metadata["num_qpd_bits"] = len(subexperiments_dict[label][i].cregs[0])

# If the input was a circuit, the output results should be a single SamplerResult instance
results_out = results
if isinstance(circuits, QuantumCircuit):
Expand Down
124 changes: 108 additions & 16 deletions circuit_knitting/cutting/cutting_experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
import numpy as np
from qiskit.circuit import QuantumCircuit, ClassicalRegister
from qiskit.quantum_info import PauliList
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import RemoveResetInZeroState, DAGFixedPoint

from ..utils.iteration import strict_zip
from ..utils.transpiler_passes import RemoveFinalReset, ConsolidateResets
from ..utils.observable_grouping import ObservableCollection, CommutingObservableGroup
from .qpd import (
WeightType,
Expand Down Expand Up @@ -54,10 +57,16 @@ def generate_cutting_experiments(
In both cases, the subexperiment lists are ordered as follows:
:math:`[sample_{0}observable_{0}, \ldots, sample_{0}observable_{N}, sample_{1}observable_{0}, \ldots, sample_{M}observable_{N}]`
:math:`[sample_{0}observable_{0}, \ldots, sample_{0}observable_{N-1}, sample_{1}observable_{0}, \ldots, sample_{M-1}observable_{N-1}]`
The coefficients will always be returned as a 1D array -- one coefficient for each unique sample.
Note that this function also runs some transpiler passes on each generated
circuit, namely :class:`~qiskit.transpiler.passes.RemoveResetInZeroState`,
:class:`.RemoveFinalReset`, and :class:`.ConsolidateResets`, in order to
remove unnecessary :class:`~qiskit.circuit.library.Reset`\ s from the
circuit that are added by the subexperiment decompositions for cut wires.
Args:
circuits: The circuit(s) to partition and separate
observables: The observable(s) to evaluate for each unique sample
Expand Down Expand Up @@ -148,12 +157,32 @@ def generate_cutting_experiments(
subcircuit = subcircuit_dict[label]
if is_separated:
map_ids_tmp = tuple(map_ids[j] for j in subcirc_map_ids[label])
decomp_qc = decompose_qpd_instructions(
subcircuit, subcirc_qpd_gate_ids[label], map_ids_tmp
)
for j, cog in enumerate(so.groups):
meas_qc = _append_measurement_circuit(decomp_qc, cog)
subexperiments_dict[label].append(meas_qc)
new_qc = _append_measurement_register(subcircuit, cog)
decompose_qpd_instructions(
new_qc, subcirc_qpd_gate_ids[label], map_ids_tmp, inplace=True
)
_append_measurement_circuit(new_qc, cog, inplace=True)
subexperiments_dict[label].append(new_qc)

# Remove initial and final resets from the subexperiments. This will
# enable the `Move` operation to work on backends that don't support
# `Reset`, as long as qubits are not re-used. See
# https://github.com/Qiskit-Extensions/circuit-knitting-toolbox/issues/452.
# While we are at it, we also consolidate each run of multiple resets
# (which can arise when re-using qubits) into a single reset.
pass_manager = PassManager()
pass_manager.append(
[
RemoveResetInZeroState(),
RemoveFinalReset(),
ConsolidateResets(),
DAGFixedPoint(),
],
do_while=lambda property_set: not property_set["dag_fixed_point"],
)
for label, subexperiments in subexperiments_dict.items():
subexperiments_dict[label] = pass_manager.run(subexperiments)

# If the input was a single quantum circuit, return the subexperiments as a list
subexperiments_out: list[QuantumCircuit] | dict[
Expand Down Expand Up @@ -231,6 +260,37 @@ def _get_bases(circuit: QuantumCircuit) -> tuple[list[QPDBasis], list[list[int]]
return bases, qpd_gate_ids


def _append_measurement_register(
qc: QuantumCircuit,
cog: CommutingObservableGroup,
/,
*,
inplace: bool = False,
):
"""Append a new classical register for the given ``CommutingObservableGroup``.
The new register will be named ``"observable_measurements"`` and will be
the final register in the returned circuit, i.e. ``retval.cregs[-1]``.
Args:
qc: The quantum circuit
cog: The commuting observable set for which to construct measurements
inplace: Whether to operate on the circuit in place (default: ``False``)
Returns:
The modified circuit
"""
if not inplace:
qc = qc.copy()

pauli_indices = _get_pauli_indices(cog)

obs_creg = ClassicalRegister(len(pauli_indices), name="observable_measurements")
qc.add_register(obs_creg)

return qc


def _append_measurement_circuit(
qc: QuantumCircuit,
cog: CommutingObservableGroup,
Expand All @@ -239,15 +299,15 @@ def _append_measurement_circuit(
qubit_locations: Sequence[int] | None = None,
inplace: bool = False,
) -> QuantumCircuit:
"""Append a new classical register and measurement instructions for the given ``CommutingObservableGroup``.
"""Append measurement instructions for the given ``CommutingObservableGroup``.
The new register will be named ``"observable_measurements"`` and will be
the final register in the returned circuit, i.e. ``retval.cregs[-1]``.
The measurement results will be placed in a register with the name
``"observable_measurements"``. Such a register can be created by calling
:func:`_append_measurement_register` before calling the current function.
Args:
qc: The quantum circuit
cog: The commuting observable set for
which to construct measurements
cog: The commuting observable set for which to construct measurements
qubit_locations: A ``Sequence`` whose length is the number of qubits
in the observables, where each element holds that qubit's corresponding
index in the circuit. By default, the circuit and observables are assumed
Expand All @@ -273,24 +333,56 @@ def _append_measurement_circuit(
f"qubit_locations has {len(qubit_locations)} element(s) but the "
f"observable(s) have {cog.general_observable.num_qubits} qubit(s)."
)

# Find observable_measurements register
for reg in qc.cregs:
if reg.name == "observable_measurements":
obs_creg = reg
break
else:
raise ValueError('Cannot locate "observable_measurements" register')

pauli_indices = _get_pauli_indices(cog)

if obs_creg.size != len(pauli_indices):
raise ValueError(
'"observable_measurements" register is the wrong size '
"for the given commuting observable group "
f"({obs_creg.size} != {len(pauli_indices)})"
)

if not inplace:
qc = qc.copy()

# Append the appropriate measurements to qc
obs_creg = ClassicalRegister(len(cog.pauli_indices), name="observable_measurements")
qc.add_register(obs_creg)
#
# Implement the necessary basis rotations and measurements, as
# in BackendEstimator._measurement_circuit().
genobs_x = cog.general_observable.x
genobs_z = cog.general_observable.z
for clbit, subqubit in enumerate(cog.pauli_indices):
for clbit, subqubit in enumerate(pauli_indices):
# subqubit is the index of the qubit in the subsystem.
# actual_qubit is its index in the system of interest (if different).
actual_qubit = qubit_locations[subqubit]
if genobs_x[subqubit]:
if genobs_z[subqubit]:
qc.sdg(actual_qubit)
qc.h(actual_qubit)
# Rotate Y basis to Z basis
qc.sx(actual_qubit)
else:
# Rotate X basis to Z basis
qc.h(actual_qubit)
# Measure in Z basis
qc.measure(actual_qubit, obs_creg[clbit])

return qc


def _get_pauli_indices(cog: CommutingObservableGroup) -> list[int]:
"""Return the indices to qubits to be measured."""
# If the circuit has no measurements, the Sampler will fail. So, we
# measure one qubit as a temporary workaround to
# https://github.com/Qiskit-Extensions/circuit-knitting-toolbox/issues/422
pauli_indices = cog.pauli_indices
if not pauli_indices:
pauli_indices = [0]
return pauli_indices
Loading

0 comments on commit 29732fc

Please sign in to comment.