Skip to content

Commit

Permalink
Created DiscreteLayerGenerator
Browse files Browse the repository at this point in the history
  • Loading branch information
mtweiden committed Aug 27, 2024
1 parent 064d58e commit 06fd289
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 0 deletions.
3 changes: 3 additions & 0 deletions bqskit/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@
:toctree: autogen
:recursive:
DiscreteLayerGenerator
FourParamGenerator
MiddleOutLayerGenerator
SeedLayerGenerator
Expand Down Expand Up @@ -274,6 +275,7 @@
from bqskit.passes.rules.zxzxz import ZXZXZDecomposition
from bqskit.passes.search.frontier import Frontier
from bqskit.passes.search.generator import LayerGenerator
from bqskit.passes.search.generators.discrete import DiscreteLayerGenerator
from bqskit.passes.search.generators.fourparam import FourParamGenerator
from bqskit.passes.search.generators.middleout import MiddleOutLayerGenerator
from bqskit.passes.search.generators.seed import SeedLayerGenerator
Expand Down Expand Up @@ -341,6 +343,7 @@
'DijkstraHeuristic',
'Frontier',
'LayerGenerator',
'DiscreteLayerGenerator',
'HeuristicFunction',
'SeedLayerGenerator',
'BlockConversionPass',
Expand Down
2 changes: 2 additions & 0 deletions bqskit/passes/search/generators/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""This package contains LayerGenerator definitions."""
from __future__ import annotations

from bqskit.passes.search.generators.discrete import DiscreteLayerGenerator
from bqskit.passes.search.generators.fourparam import FourParamGenerator
from bqskit.passes.search.generators.middleout import MiddleOutLayerGenerator
from bqskit.passes.search.generators.seed import SeedLayerGenerator
Expand All @@ -10,6 +11,7 @@
from bqskit.passes.search.generators.wide import WideLayerGenerator

__all__ = [
'DiscreteLayerGenerator',
'FourParamGenerator',
'MiddleOutLayerGenerator',
'SeedLayerGenerator',
Expand Down
213 changes: 213 additions & 0 deletions bqskit/passes/search/generators/discrete.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
"""This module implements the DiscreteLayerGenerator class."""
from __future__ import annotations

import logging
from typing import Sequence

from bqskit.compiler.passdata import PassData
from bqskit.ir.circuit import Circuit
from bqskit.ir.gate import Gate
from bqskit.ir.gates import CNOTGate
from bqskit.ir.gates import HGate
from bqskit.ir.gates import TGate
from bqskit.ir.gates.parameterized.pauliz import PauliZGate
from bqskit.ir.operation import Operation
from bqskit.passes.search.generator import LayerGenerator
from bqskit.qis.state.state import StateVector
from bqskit.qis.state.system import StateSystem
from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
from bqskit.utils.typing import is_sequence


_logger = logging.getLogger(__name__)


class DiscreteLayerGenerator(LayerGenerator):
"""
The DiscreteLayerGenerator class.
Expands circuits using only discrete gates.
"""

def __init__(
self,
gateset: Sequence[Gate] = [HGate(), TGate(), CNOTGate()],
double_headed: bool = False,
) -> None:
"""
Construct a DiscreteLayerGenerator.
Args:
gateset (Sequence[Gate]): A sequence of gates that can be used
in the output circuit. These must be non-parameterized gates.
(Default: [HGate(), TGate(), CNOTGate()])
double_headed (bool): If True, successors will be generated by
both appending and prepending gates. (Default: False)
Raises:
TypeError: If the gateset is not a sequence.
TypeError: If the gateset contains a parameterized gate.
TypeError: If the radices of gates are different.
"""
if not is_sequence(gateset):
m = f'Expected sequence of gates, got {type(gateset)}.'
raise TypeError(m)

radix = gateset[0].radixes[0]
for gate in gateset:
if gate.num_params > 0:
m = 'Expected gate for constant gates, got parameterized'
m += f' {gate} gate.'
raise TypeError(m)
for rad in gate.radixes:
if rad != radix:
m = f'Radix mismatch on gate: {gate}. '
m += f'Expected {radix}, got {rad}.'
raise TypeError(m)
self.gateset = gateset
self.double_headed = double_headed

def gen_initial_layer(
self,
target: UnitaryMatrix | StateVector | StateSystem,
data: PassData,
) -> Circuit:
"""
Generate the initial layer, see LayerGenerator for more.
Raises:
ValueError: If `target` has a radix mismatch with
`self.initial_layer_gate`.
"""

if not isinstance(target, (UnitaryMatrix, StateVector, StateSystem)):
m = f'Expected unitary or state, got {type(target)}.'
raise TypeError(m)

for radix in target.radixes:
if radix != self.gateset[0].radixes[0]:
m = 'Radix mismatch between target and gateset.'
raise ValueError(m)

init_circuit = Circuit(target.num_qudits, target.radixes)

if self.double_headed:
n = target.num_qudits
span = list(range(n))
init_circuit.append_gate(PauliZGate(n), span)

return init_circuit

def cancels_something(
self,
circuit: Circuit,
gate: Gate,
location: tuple[int, ...],
) -> bool:
"""Ensure applying gate at location does not cancel a previous gate."""
last_cycle = circuit.num_cycles - 1
try:
op = circuit.get_operation((last_cycle, location[0]))
op_gate, op_location = op.gate, op.location
if op_location == location and op_gate.get_inverse() == gate:
return True
return False
except IndexError:
return False

def count_repeats(
self,
circuit: Circuit,
gate: Gate,
qudit: int,
) -> int:
"""Count the number of times the last gate is repeated on qudit."""
count = 0
for cycle in reversed(range(circuit.num_cycles)):
try:
op = circuit.get_operation((cycle, qudit))
if op.gate == gate:
count += 1
else:
return count
except IndexError:
continue
return count

def gen_successors(self, circuit: Circuit, data: PassData) -> list[Circuit]:
"""
Generate the successors of a circuit node.
Raises:
ValueError: If circuit is a single-qudit circuit.
"""
if not isinstance(circuit, Circuit):
raise TypeError(f'Expected circuit, got {type(circuit)}.')

if circuit.num_qudits < 2:
raise ValueError('Cannot expand a single-qudit circuit.')

# Get the coupling graph
coupling_graph = data.connectivity

# Generate successors
successors = []
hashes = set()
singles = [gate for gate in self.gateset if gate.num_qudits == 1]
multis = [gate for gate in self.gateset if gate.num_qudits > 1]

for gate in singles:
for qudit in range(circuit.num_qudits):
if self.cancels_something(circuit, gate, (qudit,)):
continue
if isinstance(gate, TGate):
if self.count_repeats(circuit, TGate(), qudit) >= 7:
continue
successor = circuit.copy()
successor.append_gate(gate, [qudit])

h = hash_circuit_structure(successor)
if h not in hashes:
successors.append(successor)
hashes.add(h)

if self.double_headed:
successor = circuit.copy()
op = Operation(gate, [qudit])
successor.insert(0, op)
h = hash_circuit_structure(successor)
if h not in hashes:
successors.append(successor)
hashes.add(h)

for gate in multis:
for edge in coupling_graph:
if self.cancels_something(circuit, gate, edge):
continue
successor = circuit.copy()
successor.append_gate(gate, edge)
h = hash_circuit_structure(successor)
if h not in hashes:
successors.append(successor)
hashes.add(h)

if self.double_headed:
successor = circuit.copy()
op = Operation(gate, edge)
successor.insert(0, op)
h = hash_circuit_structure(successor)
if h not in hashes:
successors.append(successor)
hashes.add(h)

return successors


def hash_circuit_structure(circuit: Circuit) -> int:
hashes = []
for op in circuit:
hashes.append(hash(op))
return hash(tuple(hashes))
51 changes: 51 additions & 0 deletions tests/passes/search/generators/test_discrete.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from __future__ import annotations

from random import randint

from bqskit.compiler.passdata import PassData
from bqskit.ir.circuit import Circuit
from bqskit.ir.gates import CNOTGate
from bqskit.ir.gates import HGate
from bqskit.ir.gates import TGate
from bqskit.passes.search.generators import DiscreteLayerGenerator


class TestDiscreteLayerGenerator:

def test_gate_set(self) -> None:
gates = [HGate(), CNOTGate(), TGate()]
generator = DiscreteLayerGenerator()
assert all(g in generator.gateset for g in gates)

def test_double_headed(self) -> None:
single_gen = DiscreteLayerGenerator(double_headed=False)
double_gen = DiscreteLayerGenerator(double_headed=True)
base = Circuit(4)
single_sucs = single_gen.gen_successors(base, PassData(base))
double_sucs = double_gen.gen_successors(base, PassData(base))
assert len(single_sucs) == len(double_sucs)

base = Circuit(2)
base.append_gate(CNOTGate(), (0, 1))
single_sucs = single_gen.gen_successors(base, PassData(base))
double_sucs = double_gen.gen_successors(base, PassData(base))
assert len(single_sucs) < len(double_sucs)
assert all(c in double_sucs for c in single_sucs)

def test_cancels_something(self) -> None:
gen = DiscreteLayerGenerator()
base = Circuit(2)
base.append_gate(HGate(), (0,))
base.append_gate(TGate(), (0,))
base.append_gate(HGate(), (0,))
assert gen.cancels_something(base, HGate(), (0,))
assert not gen.cancels_something(base, HGate(), (1,))
assert not gen.cancels_something(base, TGate(), (0,))

def test_count_repeats(self) -> None:
num_repeats = randint(1, 50)
c = Circuit(1)
for _ in range(num_repeats):
c.append_gate(HGate(), (0,))
gen = DiscreteLayerGenerator()
assert gen.count_repeats(c, HGate(), 0) == num_repeats

0 comments on commit 06fd289

Please sign in to comment.