diff --git a/bqskit/passes/__init__.py b/bqskit/passes/__init__.py index 9a386f6fb..ef8034be9 100644 --- a/bqskit/passes/__init__.py +++ b/bqskit/passes/__init__.py @@ -195,6 +195,7 @@ :toctree: autogen :recursive: + DiscreteLayerGenerator FourParamGenerator MiddleOutLayerGenerator SeedLayerGenerator @@ -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 @@ -341,6 +343,7 @@ 'DijkstraHeuristic', 'Frontier', 'LayerGenerator', + 'DiscreteLayerGenerator', 'HeuristicFunction', 'SeedLayerGenerator', 'BlockConversionPass', diff --git a/bqskit/passes/search/generators/__init__.py b/bqskit/passes/search/generators/__init__.py index aec7fab2e..308667cea 100644 --- a/bqskit/passes/search/generators/__init__.py +++ b/bqskit/passes/search/generators/__init__.py @@ -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 @@ -10,6 +11,7 @@ from bqskit.passes.search.generators.wide import WideLayerGenerator __all__ = [ + 'DiscreteLayerGenerator', 'FourParamGenerator', 'MiddleOutLayerGenerator', 'SeedLayerGenerator', diff --git a/bqskit/passes/search/generators/discrete.py b/bqskit/passes/search/generators/discrete.py new file mode 100644 index 000000000..6b8d3f10a --- /dev/null +++ b/bqskit/passes/search/generators/discrete.py @@ -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)) diff --git a/tests/passes/search/generators/test_discrete.py b/tests/passes/search/generators/test_discrete.py new file mode 100644 index 000000000..b60473729 --- /dev/null +++ b/tests/passes/search/generators/test_discrete.py @@ -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