Skip to content

Commit

Permalink
Merge branch 'LO-circuit-cuts-optimizer' of https://github.com/Qiskit…
Browse files Browse the repository at this point in the history
…-Extensions/circuit-knitting-toolbox into LO-circuit-cuts-optimizer
  • Loading branch information
ibrahim-shehzad committed Jan 10, 2024
2 parents 3e10f89 + 8e30782 commit eabf648
Show file tree
Hide file tree
Showing 5 changed files with 369 additions and 39 deletions.
2 changes: 2 additions & 0 deletions circuit_knitting/cutting/cut_finding/circuit_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,10 +361,12 @@ def exportSubcircuitsAsString(self, name_mapping="default"):
wire_map = self.makeWireMapping(name_mapping)

out = list(range(self.getNumWires()))
# print('wire_map:', wire_map)
alphabet = string.ascii_uppercase + string.ascii_lowercase
for k, subcircuit in enumerate(self.subcircuits):
for wire in subcircuit:
out[wire_map[wire]] = alphabet[k]
# print('subcircuits:', self.subcircuits)
return "".join(out)

def makeWireMapping(self, name_mapping):
Expand Down
74 changes: 72 additions & 2 deletions circuit_knitting/cutting/cut_finding/cut_finding.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,84 @@
from __future__ import annotations

from qiskit import QuantumCircuit
from qiskit.circuit import CircuitInstruction

from .optimization_settings import OptimizationSettings
from .quantum_device_constraints import DeviceConstraints
from .circuit_interface import SimpleGateList
from .lo_cuts_optimizer import LOCutsOptimizer
from .utils import QCtoCCOCircuit
from ..instructions import CutWire
from ..cutting_decomposition import cut_gates


def find_cuts(
circuit: QuantumCircuit,
optimization: OptimizationSettings | dict[str, str | int],
constraints: DeviceConstraints | dict[str, int],
):
pass
) -> QuantumCircuit:
"""
Find cut locations in a circuit, given optimization settings and QPU constraints.
Args:
circuit: The circuit to cut
optimization: Settings for controlling optimizer behavior. Currently,
only a best-first optimizer is supported. For a list of supported
optimization settings, see :class:`.OptimizationSettings`.
constraints: QPU constraints used to generate the cut location search space.
For information on how to specify QPU constraints, see :class:`.DeviceConstraints`.
Returns:
A circuit containing :class:`.BaseQPDGate` instances. The subcircuits
resulting from cutting these gates will be runnable on the devices
specified in ``constraints``.
"""
circuit_cco = QCtoCCOCircuit(circuit)
interface = SimpleGateList(circuit_cco)

if isinstance(optimization, dict):
opt_settings = OptimizationSettings.from_dict(optimization)
else:
opt_settings = optimization

# Hard-code the optimization type to best-first
opt_settings.setEngineSelection("CutOptimization", "BestFirst")

if isinstance(constraints, dict):
constraint_settings = DeviceConstraints.from_dict(constraints)
else:
constraint_settings = constraints

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

# Find cut locations
opt_out = optimizer.optimize()

wire_cut_actions = []
gate_ids = []
for action in opt_out.actions:
if action[0].getName() == "CutTwoQubitGate":
gate_ids.append(action[1][0])
else:
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]):
if action[0].getName() == "CutTwoQubitGate":
continue
inst_id = action[1][0]
qubit_id = action[2][0][0] - 1
circ_out.data.insert(
inst_id + counter,
CircuitInstruction(CutWire(), [circuit.data[inst_id].qubits[qubit_id]], []),
)
counter += 1

return circ_out
53 changes: 25 additions & 28 deletions circuit_knitting/cutting/cut_finding/optimization_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@

"""Class for specifying parameters that control the optimization."""

from __future__ import annotations

from dataclasses import dataclass

class OptimizationSettings:

@dataclass
class OptimizationSettings:
"""Class for specifying parameters that control the optimization.
Member Variables:
Expand Down Expand Up @@ -70,44 +74,33 @@ class OptimizationSettings:
ValueError: beam_width must be a positive definite integer.
"""

def __init__(
self,
max_gamma=1024,
max_backjumps=10000,
greedy_multiplier=None,
beam_width=30,
rand_seed=None,
LO=True,
LOCC_ancillas=False,
LOCC_no_ancillas=False,
engine_selections={"PhaseOneStageOneNoQubitReuse": "Greedy"},
):
if not (isinstance(max_gamma, int) and max_gamma > 0):
max_gamma: int = 1024
max_backjumps: int = 10_000
greedy_multiplier: float | int | None = None
beam_width: int = 30
rand_seed: int | None = None
LO: bool = True
LOCC_ancillas: bool = False
LOCC_no_ancillas: bool = False
engine_selections: dict[str, str] | None = None

def __post_init__(self):
if self.max_gamma < 1:
raise ValueError("max_gamma must be a positive definite integer.")

if not (isinstance(max_backjumps, int) and max_backjumps >= 0):
if self.max_backjumps < 0:
raise ValueError("max_backjumps must be a positive semi-definite integer.")

if not (isinstance(beam_width, int) and beam_width > 0):
if self.beam_width < 1:
raise ValueError("beam_width must be a positive definite integer.")

self.max_gamma = max_gamma
self.max_backjumps = max_backjumps
self.greedy_multiplier = greedy_multiplier
self.beam_width = beam_width
self.rand_seed = rand_seed
self.engine_selections = engine_selections.copy()
self.LO = LO
self.LOCC_ancillas = LOCC_ancillas
self.LOCC_no_ancillas = LOCC_no_ancillas

self.gate_cut_LO = self.LO
self.gate_cut_LOCC_with_ancillas = self.LOCC_ancillas
self.gate_cut_LOCC_no_ancillas = self.LOCC_no_ancillas

self.wire_cut_LO = self.LO
self.wire_cut_LOCC_with_ancillas = self.LOCC_ancillas
self.wire_cut_LOCC_no_ancillas = self.LOCC_no_ancillas
if self.engine_selections is None:
self.engine_selections = {"PhaseOneStageOneNoQubitReuse": "Greedy"}

def getMaxGamma(self):
"""Return the max gamma."""
Expand Down Expand Up @@ -188,3 +181,7 @@ def getCutSearchGroups(self):
out.append("WireCut")

return out

@classmethod
def from_dict(cls, options: dict[str, int]):
return cls(**options)
25 changes: 16 additions & 9 deletions circuit_knitting/cutting/cut_finding/quantum_device_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@

"""Class used for specifying characteristics of the target QPU."""

from __future__ import annotations

from dataclasses import dataclass

class DeviceConstraints:

@dataclass
class DeviceConstraints:
"""Class for specifying the characteristics of the target quantum
processor that the optimizer must respect in order for the resulting
subcircuits to be executable on the target processor.
Expand All @@ -31,16 +35,19 @@ class DeviceConstraints:
ValueError: num_QPUs must be a positive integer.
"""

def __init__(self, qubits_per_QPU, num_QPUs):
if not (isinstance(qubits_per_QPU, int) and qubits_per_QPU > 0):
raise ValueError("qubits_per_QPU must be a positive definite integer.")
qubits_per_QPU: int
num_QPUs: int

if not (isinstance(num_QPUs, int) and num_QPUs > 0):
raise ValueError("num_QPUs must be a positive definite integer.")

self.qubits_per_QPU = qubits_per_QPU
self.num_QPUs = num_QPUs
def __post_init__(self):
if self.qubits_per_QPU < 1 or self.num_QPUs < 1:
raise ValueError(
"qubits_per_QPU and num_QPUs must be positive definite integers."
)

def getQPUWidth(self):
"""Return the number of qubits supported on each individual QPU."""
return self.qubits_per_QPU

@classmethod
def from_dict(cls, options: dict[str, int]):
return cls(**options)
254 changes: 254 additions & 0 deletions docs/circuit_cutting/tutorials/04_automatic_cut_finding.ipynb

Large diffs are not rendered by default.

0 comments on commit eabf648

Please sign in to comment.