Skip to content

Commit

Permalink
Add automated cut-finding module (#520)
Browse files Browse the repository at this point in the history
* set up new branch with LO circuit cut finder

* Update tutorial with bug fix pending

* update tutorial with bug fix pending

* fix bug and update notebook

* update print method, Co-authored by: Edwin Pednault [email protected]

* Update cut finding dir structure

* commit before pulling changes

* Add license blurbs

* black errors

* clean up notebook with updated print method

* clean up printed output in tutorial

* Add find_cuts tutorial

* Use dataclasses for settings objects

* Update tutorial to integrate with CKT

* edit string output function

* clean up utils doc strings.

* Simplifications in xform func

* black

* update xform code to fix small bug

* minor simplification

* edit doc strings

* edit field name in settings

* Introduce a CircuitElement tuple

* Remove remnants of other search algorithms.

* Fix cost lookup logic

* snapshot test notebook before pull

* Add cut finder tests

* rebase conflict

* Add license blurbs

* black errors

* clean up notebook with updated print method

* clean up printed output in tutorial

* edit string output function

* Add find_cuts tutorial

* Use dataclasses for settings objects

* Update tutorial to integrate with CKT

* clean up utils doc strings.

* Simplifications in xform func

* black

* update xform code to fix small bug

* minor simplification

* edit doc strings

* edit field name in settings

* Remove remnants of other search algorithms.

* Introduce a CircuitElement tuple

* Fix cost lookup logic

* Fix bugs with wire cutting

* Update cco tests

* Update indices to params in gate dict

* re run tutorial for correct outputs

* clean up notebook

* clean up tutorial

* clean up tutorial

* clean up tutorial

* clean up tutorial and print statement

* Add Ed Pednault as author

* Set gamma UP and LB both to gamma until bell pair cutting is supported in CKT

* Remove redundant CircuitElement class

* Remove unnecessary init

* Add partial type hints for a few modules

* Remove unused code and add roundtrip tests.

* Update BFS test and remove unused code.

* Fix black errors, changes to tests pending

* Update test to match new circuit interfaces

* Update circuit interface tests and some docstrings.

* Update CCOtoQC func and associated test.

* Update BFS test and run style.

* Finish making all tests consistent with new circuit interface

* Add and update tests

* Add and update tests

* Add type hints, tests, update func names.

* add tests, fix some mypy errors.

* Correct mypy errors, fix bugs in tests.

* Remove cast subscripting to make compatible with py38.

* Make type annotations compatible with min version tests.

* Fix more py38 type compatibility issues.

* Fix type hints.

* Get rid of camel casing in function names and fix linting errors in doc strings.

* Fix remaining pylint errors.

* Generate black diff.

* Use python3.10 for lint workflow

This will match the version specified by basepython in `tox.ini`

* Run style with updated version of black

* Edit doc strings

* Find cuts updates (#498)

* Return metadata from find_cuts

* Move cut finding into cutting package

* black

* Fix coverage

* notebook update

* Docstring

* Fix small bugs in find_cuts

* black

* Update test/cutting/test_cutting_decomposition.py

* Handle CutBothWires action

* Clean up todo

* Assert the qubit id's are relative to a two qubit gate

* elif

* minor comment update

* Clean up docstring/comments

* Clean up docstring

* Don't use funky logic

* Docstring cleanup

* Change to namedtuples, redo tests and typing.

* Fix sphinx errors, change actions to named tuples.

* lint

* clean up notebook, cast statements.

* Clean up type hints

* style

* Add tests, add qubit list to output.

* style

* Add tests

* Remove subscripting in cast statements

* Fix tests.

* Fix docstring. Improve error message. Add test.

* Update tutorial, add classes for constraints and settings

* Fix test, edit doc string.

* Correct error in tutorial

* Correct tutorial output

* Remove num_QPUs, add tests.

* Edit doc string, fix type hint for backjumps.

* Fix bug in indexing in find_cuts, and fix docstring

* Fix funky rendering

* docstring

* Fix remnant of indexing bug.

* Fix remnant of indexing bug.

* Remove typo in cutting_optimization, update doc string.

* Use new opt settings class

* Update to new class in cutting pkg

* Fix style, docstring.

* release note

* Edit release notes, clean up tutorial

* Change to qubits per subcircuit everywhere

* Expand release note

* Create find_cuts module

* Ignore CutBothWires for coverage

* Fix coverage

* black

* Clean up docstring

* Improve docstring

* Upeate release notes

* Remove extraneous tutorial

* Add test, edit docstrings.

* Fix docstring typo

* Docstring

* Import test circuit from qasm, edit docstrings and release notes.

* Correct path to test circuit

* Edit docstring

* Move find_cuts down into cut finding section of docs

* Update circuit_knitting/cutting/__init__.py

Co-authored-by: Jim Garrison <[email protected]>

* Construct test circuits in functions, not module (#521)

Follow up to #520 (comment)

These are still not _fixtures_, but at least they will only be run
if the test is executed.

* Change import routing, fix doc strings, add license blurbs.

* Move the cut-finder settings up into  module.

* black

* coverage

* black

* Update circuit_knitting/cutting/__init__.py

Co-authored-by: Jim Garrison <[email protected]>

* Rename find_cuts module to automated_cut_finding

* test import

* cut_finding test imports

* Remove reference to OptimizationSettings in OptimizationParameters class

* Move tests, fix type hints and imports

* Update import

Co-authored-by: Jim Garrison <[email protected]>

---------

Co-authored-by: Caleb Johnson <[email protected]>
Co-authored-by: Edwin Pednault <[email protected]>
Co-authored-by: Jim Garrison <[email protected]>
  • Loading branch information
4 people authored Mar 29, 2024
1 parent 70c47d9 commit 7ff3b5d
Show file tree
Hide file tree
Showing 26 changed files with 4,810 additions and 3 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python 3.9
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: '3.10'
- name: Install tox
run: |
python -m pip install --upgrade pip
Expand Down
21 changes: 21 additions & 0 deletions circuit_knitting/cutting/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,23 @@
instructions.CutWire
instructions.Move
Automatic Cut Finding
~~~~~~~~~~~~~~~~~~~~~
.. autosummary::
:toctree: ../stubs/
:nosignatures:
find_cuts
.. autosummary::
:toctree: ../stubs/
:nosignatures:
:template: autosummary/class_no_inherited_members.rst
OptimizationParameters
DeviceConstraints
Quasi-Probability Decomposition (QPD)
=====================================
Expand Down Expand Up @@ -85,6 +102,7 @@
from .cutting_experiments import generate_cutting_experiments
from .cutting_reconstruction import reconstruct_expectation_values
from .wire_cutting_transforms import cut_wires, expand_observables
from .automated_cut_finding import find_cuts, DeviceConstraints, OptimizationParameters

__all__ = [
"partition_circuit_qubits",
Expand All @@ -95,4 +113,7 @@
"PartitionedCuttingProblem",
"cut_wires",
"expand_observables",
"find_cuts",
"DeviceConstraints",
"OptimizationParameters",
]
157 changes: 157 additions & 0 deletions circuit_knitting/cutting/automated_cut_finding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# This code is a Qiskit project.

# (C) Copyright IBM 2024.

# 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.

"""Function for automatically finding locations for gate and wire cuts."""

from __future__ import annotations

from typing import cast, Any
from dataclasses import dataclass

from qiskit.circuit import QuantumCircuit, CircuitInstruction

from .instructions import CutWire
from .cutting_decomposition import cut_gates
from .cut_finding.optimization_settings import OptimizationSettings
from .cut_finding.disjoint_subcircuits_state import DisjointSubcircuitsState
from .cut_finding.circuit_interface import SimpleGateList
from .cut_finding.lo_cuts_optimizer import LOCutsOptimizer
from .cut_finding.cco_utils import qc_to_cco_circuit


def find_cuts(
circuit: QuantumCircuit,
optimization: OptimizationParameters,
constraints: DeviceConstraints,
) -> tuple[QuantumCircuit, dict[str, float]]:
"""Find cut locations in a circuit, given optimization parameters and cutting constraints.
Args:
circuit: The circuit to cut. The input circuit may not contain gates acting
on more than two qubits.
optimization: Options for controlling optimizer behavior. Currently, the optimal
cuts are chosen using Dijkstra's best-first search algorithm.
constraints: Constraints on how the circuit may be partitioned
Returns:
A circuit containing :class:`.BaseQPDGate` instances. The subcircuits
resulting from cutting these gates will be runnable on the devices meeting
the ``constraints``.
A metadata dictionary:
- cuts: A list of length-2 tuples describing each cut in the output circuit.
The tuples are formatted as ``(cut_type: str, cut_id: int)``. The
cut ID is the index of the cut gate or wire in the output circuit's
``data`` field.
- sampling_overhead: The sampling overhead incurred from cutting the specified
gates and wires.
Raises:
ValueError: The input circuit contains a gate acting on more than 2 qubits.
"""
circuit_cco = qc_to_cco_circuit(circuit)
interface = SimpleGateList(circuit_cco)

opt_settings = OptimizationSettings(
seed=optimization.seed,
max_gamma=optimization.max_gamma,
max_backjumps=optimization.max_backjumps,
)

# Hard-code the optimizer to an LO-only optimizer
optimizer = LOCutsOptimizer(interface, opt_settings, constraints)

# Find cut locations
opt_out = optimizer.optimize()

wire_cut_actions = []
gate_ids = []

opt_out = cast(DisjointSubcircuitsState, opt_out)
opt_out.actions = cast(list, opt_out.actions)
for action in opt_out.actions:
if action.action.get_name() == "CutTwoQubitGate":
gate_ids.append(action.gate_spec.instruction_id)
else:
# The cut-finding optimizer currently only supports 4 cutting
# actions: {CutTwoQubitGate + these 3 wire cut types}
assert action.action.get_name() in (
"CutLeftWire",
"CutRightWire",
"CutBothWires",
)
wire_cut_actions.append(action)

# First, replace all gates to cut with BaseQPDGate instances.
# This assumes each gate to cut is replaced 1-to-1 with a QPD gate.
# This may not hold in the future as we stop treating gate cuts individually.
circ_out = cut_gates(circuit, gate_ids)[0]

# Insert all the wire cuts
counter = 0
for action in sorted(wire_cut_actions, key=lambda a: a[1][0]):
inst_id = action.gate_spec.instruction_id
# action.args[0][0] will be either 1 (control) or 2 (target)
qubit_id = action.args[0][0] - 1
circ_out.data.insert(
inst_id + counter,
CircuitInstruction(CutWire(), [circuit.data[inst_id].qubits[qubit_id]], []),
)
counter += 1

if action.action.get_name() == "CutBothWires": # pragma: no cover
# There should be two wires specified in the action in this case
assert len(action.args) == 2
qubit_id2 = action.args[1][0] - 1
circ_out.data.insert(
inst_id + counter,
CircuitInstruction(
CutWire(), [circuit.data[inst_id].qubits[qubit_id2]], []
),
)
counter += 1

# Return metadata describing the cut scheme
metadata: dict[str, Any] = {"cuts": []}
for i, inst in enumerate(circ_out.data):
if inst.operation.name == "qpd_2q":
metadata["cuts"].append(("Gate Cut", i))
elif inst.operation.name == "cut_wire":
metadata["cuts"].append(("Wire Cut", i))
metadata["sampling_overhead"] = opt_out.upper_bound_gamma() ** 2

return circ_out, metadata


@dataclass
class OptimizationParameters:
"""Specify parameters that control the optimization."""

seed: int | None = OptimizationSettings().seed
max_gamma: float = OptimizationSettings().max_gamma
max_backjumps: None | int = OptimizationSettings().max_backjumps


@dataclass
class DeviceConstraints:
"""Specify the constraints (qubits per subcircuit) that must be respected."""

qubits_per_subcircuit: int

def __post_init__(self):
"""Post-init method for data class."""
if self.qubits_per_subcircuit < 1:
raise ValueError(
"qubits_per_subcircuit must be a positive definite integer."
)

def get_qpu_width(self) -> int:
"""Return the number of qubits per subcircuit."""
return self.qubits_per_subcircuit
10 changes: 10 additions & 0 deletions circuit_knitting/cutting/cut_finding/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# (C) Copyright IBM 2024.

# 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.

"""Main automated cut finding functionality."""
Loading

0 comments on commit 7ff3b5d

Please sign in to comment.