Skip to content

Commit

Permalink
Compiler Top-Down Integration (#49)
Browse files Browse the repository at this point in the history
## Description

This PR integrates the compiler in fully automated fashion from the
circuit to the simulations results.
- Introduced gate and circuit compilation.
- Integrated fully mixed-dimensional, two-body gate compilation through
compiler and ansatz optimization.
- Introduced a lookahead strategy and data structure (Lanes) and a
resynthesis tool for local gate compilation.
- Integrated Givens Z rotation compression and removal.
- Introduced a layer of abstraction using "blocks": crtot, crz, and
pswap objects.
- Modified matrix multiplications.
- Explored sparsification algorithms.
- Saved mappings and re-encoding of simulation results.
- Fixed MS, LS, Perm gates, and matrix factory.
- Fixed energy level graph for fake backends.


## Checklist:
- [X] The pull request only contains commits that are related to it.
- [X] I have added appropriate tests and documentation.
- [ ] I have made sure that all CI jobs on GitHub pass.
- [X] The pull request follows the project's style guidelines.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
KevinMTO and pre-commit-ci[bot] authored Aug 22, 2024
1 parent a65af54 commit a285729
Show file tree
Hide file tree
Showing 66 changed files with 3,084 additions and 693 deletions.
2 changes: 1 addition & 1 deletion src/mqt/qudits/compiler/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from .compiler_pass import CompilerPass
from .dit_manager import QuditCompiler
from .dit_compiler import QuditCompiler

__all__ = [
"CompilerPass",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ def gate_expand_to_circuit(gate, circuits_size, target, dims=None):
msg = "target must be integer < integer circuits_size"
raise ValueError(msg)

upper = [np.identity(dims[i], dtype="complex") for i in range(circuits_size - target - 1)]
upper = [np.identity(dims[i], dtype="complex") for i in range(target + 1, circuits_size)]
lower = [np.identity(dims[j], dtype="complex") for j in range(target)]
circ = [*upper, gate, *lower]
circ = [*lower, gate, *upper]
res = circ[-1]

for i in reversed(list(range(1, len(circ)))):
Expand Down
10 changes: 9 additions & 1 deletion src/mqt/qudits/compiler/compiler_pass.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from mqt.qudits.quantum_circuit import QuantumCircuit
from mqt.qudits.quantum_circuit.gate import Gate


class CompilerPass(ABC):
def __init__(self, backend, **kwargs) -> None:
self.backend = backend

def transpile_gate(self, gate: Gate) -> list[Gate]:
pass

@abstractmethod
def transpile(self, circuit):
def transpile(self, circuit: QuantumCircuit) -> QuantumCircuit:
pass
79 changes: 79 additions & 0 deletions src/mqt/qudits/compiler/dit_compiler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from __future__ import annotations

from ..core.lanes import Lanes
from ..quantum_circuit.components.extensions.gate_types import GateTypes
from .naive_local_resynth import NaiveLocResynthOptPass
from .onedit import LogLocQRPass, PhyLocAdaPass, PhyLocQRPass, ZPropagationOptPass, ZRemovalOptPass
from .twodit import LogEntQRCEXPass
from .twodit.entanglement_qr.phy_ent_qr_cex_decomp import PhyEntQRCEXPass


class QuditCompiler:
passes_enabled = {
"PhyLocQRPass": PhyLocQRPass,
"PhyLocAdaPass": PhyLocAdaPass,
"LocQRPass": PhyLocQRPass,
"LocAdaPass": PhyLocAdaPass,
"LogLocQRPass": LogLocQRPass,
"ZPropagationOptPass": ZPropagationOptPass,
"ZRemovalOptPass": ZRemovalOptPass,
"LogEntQRCEXPass": LogEntQRCEXPass,
"PhyEntQRCEXPass": PhyEntQRCEXPass,
"NaiveLocResynthOptPass": NaiveLocResynthOptPass,
}

def __init__(self) -> None:
pass

def compile(self, backend, circuit, passes_names):
passes_dict = {}
new_instr = []
# Instantiate and execute created classes
for compiler_pass in passes_names:
compiler_pass = self.passes_enabled[compiler_pass]
decomposition = compiler_pass(backend)
if "Loc" in str(compiler_pass):
passes_dict[GateTypes.SINGLE] = decomposition
elif "Ent" in str(compiler_pass):
passes_dict[GateTypes.TWO] = decomposition
elif "Multi" in str(compiler_pass):
passes_dict[GateTypes.MULTI] = decomposition
for gate in circuit.instructions:
decomposer = passes_dict.get(gate.gate_type)
new_instructions = decomposer.transpile_gate(gate)
new_instr.extend(new_instructions)

circuit.set_instructions(new_instr)
circuit.set_mapping([graph.log_phy_map for graph in backend.energy_level_graphs])

return circuit

def compile_O0(self, backend, circuit):
passes = ["PhyLocQRPass", "PhyEntQRCEXPass"]
compiled = self.compile(backend, circuit, passes)

mappings = []
for i, graph in enumerate(backend.energy_level_graphs):
mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]])
compiled.set_mapping(mappings)
return compiled

def compile_O1(self, backend, circuit):
phyloc = PhyLocAdaPass(backend)
phyent = PhyEntQRCEXPass(backend)

lanes = Lanes(circuit)
new_instructions = []
for gate in circuit.instructions:
if gate.gate_type is GateTypes.SINGLE:
ins = phyloc.transpile_gate(gate, lanes.next_is_local(gate))
new_instructions.extend(ins)
else:
ins = phyent.transpile_gate(gate)
new_instructions.extend(ins)
transpiled_circuit = circuit.copy()
mappings = []
for i, graph in enumerate(backend.energy_level_graphs):
mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]])
transpiled_circuit.set_mapping(mappings)
return transpiled_circuit.set_instructions(new_instructions)
26 changes: 0 additions & 26 deletions src/mqt/qudits/compiler/dit_manager.py

This file was deleted.

7 changes: 7 additions & 0 deletions src/mqt/qudits/compiler/naive_local_resynth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from __future__ import annotations

from mqt.qudits.compiler.naive_local_resynth.local_resynth import NaiveLocResynthOptPass

__all__ = [
"NaiveLocResynthOptPass",
]
46 changes: 46 additions & 0 deletions src/mqt/qudits/compiler/naive_local_resynth/local_resynth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from __future__ import annotations

from typing import NoReturn

import numpy as np

from mqt.qudits.compiler import CompilerPass
from mqt.qudits.core.lanes import Lanes
from mqt.qudits.quantum_circuit.components.extensions.gate_types import GateTypes
from mqt.qudits.quantum_circuit.gates import CustomOne


class NaiveLocResynthOptPass(CompilerPass):
def __init__(self, backend) -> None:
super().__init__(backend)

def transpile_gate(self, gate) -> NoReturn:
raise NotImplementedError

def transpile(self, circuit):
self.circuit = circuit
self.lanes = Lanes(self.circuit)

for line in sorted(self.lanes.index_dict.keys()):
grouped_line = self.lanes.find_consecutive_singles(self.lanes.index_dict[line])
new_line = []
for group in grouped_line[line]:
if group[0][1].gate_type == GateTypes.SINGLE:
matrix = np.identity(self.circuit.dimensions[line])
for gate_tuple in group:
gate = gate_tuple[1]
gm = gate.to_matrix()
matrix = gm @ matrix
new_line.append((
group[0][0],
CustomOne(self.circuit, "CUm", line, matrix, self.circuit.dimensions[line]),
))
else:
new_line.append(group[0])

self.lanes.index_dict[line] = new_line

new_instructions = self.lanes.extract_instructions()

transpiled_circuit = self.circuit.copy()
return transpiled_circuit.set_instructions(new_instructions)
6 changes: 3 additions & 3 deletions src/mqt/qudits/compiler/onedit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from .local_phases_transpilation import ZPropagationPass, ZRemovalPass
from .local_phases_transpilation import ZPropagationOptPass, ZRemovalOptPass
from .mapping_aware_transpilation import PhyLocAdaPass, PhyLocQRPass
from .mapping_un_aware_transpilation import LogLocAdaPass, LogLocQRPass

Expand All @@ -9,6 +9,6 @@
"LogLocQRPass",
"PhyLocAdaPass",
"PhyLocQRPass",
"ZPropagationPass",
"ZRemovalPass",
"ZPropagationOptPass",
"ZRemovalOptPass",
]
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import annotations

from .propagate_virtrz import ZPropagationPass
from .remove_phase_rotations import ZRemovalPass
from .propagate_virtrz import ZPropagationOptPass
from .remove_phase_rotations import ZRemovalOptPass

__all__ = [
"ZPropagationPass",
"ZRemovalPass",
"ZPropagationOptPass",
"ZRemovalOptPass",
]
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
from ...compilation_minitools import pi_mod


class ZPropagationPass(CompilerPass):
class ZPropagationOptPass(CompilerPass):
def __init__(self, backend, back=True) -> None:
super().__init__(backend)
self.back = back

def transpile_gate(self, gate):
return gate

def transpile(self, circuit):
return self.remove_z(circuit, self.back)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
from ... import CompilerPass


class ZRemovalPass(CompilerPass):
class ZRemovalOptPass(CompilerPass):
def __init__(self, backend) -> None:
super().__init__(backend)

def transpile_gate(self, gate):
return gate

def transpile(self, circuit):
circuit = self.remove_initial_rz(circuit)
return self.remove_trailing_rz_sequence(circuit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,61 +25,33 @@ class PhyLocAdaPass(CompilerPass):
def __init__(self, backend) -> None:
super().__init__(backend)

def transpile_gate(self, gate, vrz_prop=False):
energy_graph_i = self.backend.energy_level_graphs[gate._target_qudits]

QR = PhyQrDecomp(gate, energy_graph_i)

_decomp, algorithmic_cost, total_cost = QR.execute()

Adaptive = PhyAdaptiveDecomposition(
gate, energy_graph_i, (algorithmic_cost, total_cost), gate._dimensions, Z_prop=vrz_prop
)
(matrices_decomposed, _best_cost, new_energy_level_graph) = Adaptive.execute()

self.backend.energy_level_graphs[gate._target_qudits] = new_energy_level_graph
return [op.dag() for op in reversed(matrices_decomposed)]

def transpile(self, circuit):
self.circuit = circuit
instructions = circuit.instructions
new_instructions = []

for gate in instructions:
if gate.gate_type == GateTypes.SINGLE:
energy_graph_i = self.backend.energy_level_graphs[gate._target_qudits]
# ini_lpmap = list(self.backend.energy_level_graphs.log_phy_map)

# if self.verify:
# recover_dict = {}
# inode = self.energy_level_graph._1stInode
# if 'phase_storage' in self.energy_level_graph.nodes[inode]:
# for i in range(len(list(self.energy_level_graph.nodes))):
# thetaZ = newMod(self.energy_level_graph.nodes[i]['phase_storage'])
# recover_dict[i] = thetaZ

QR = PhyQrDecomp(gate, energy_graph_i)

_decomp, algorithmic_cost, total_cost = QR.execute()

Adaptive = PhyAdaptiveDecomposition(
gate, energy_graph_i, (algorithmic_cost, total_cost), gate._dimensions
)

(
matrices_decomposed,
_best_cost,
self.backend.energy_level_graphs[gate._target_qudits],
) = Adaptive.execute()

# if self.verify:
# nodes = list(self.energy_level_graph.nodes)
# lpmap = list(self.energy_level_graph.log_phy_map)
#
# Vgate = deepcopy(gate_matrix)
# inode = self.energy_level_graph._1stInode
#
# if 'phase_storage' in self.energy_level_graph.nodes[inode]:
# for i in range(len(recover_dict)):
# if abs(recover_dict[i]) > 1.0e-4:
# phase_gate = Rz(-recover_dict[i], i, self.dimension) # logical rotation
# Vgate = Custom_Unitary(matmul(phase_gate.matrix, Vgate.matrix), self.dimension)
#
# V = Verifier(matrices_decomposed, Vgate, nodes, ini_lpmap, lpmap, self.dimension)
# Vr = V.verify()
#
# if not Vr:
# raise Exception
new_instructions += matrices_decomposed
# circuit.replace_gate(i, matrices_decomposed)
gate_trans = self.transpile_gate(gate)
new_instructions.extend(gate_trans)
gc.collect()
else:
new_instructions.append(gate) # TODO REENCODING
new_instructions.append(gate)
transpiled_circuit = self.circuit.copy()
return transpiled_circuit.set_instructions(new_instructions)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,25 @@ class PhyLocQRPass(CompilerPass):
def __init__(self, backend) -> None:
super().__init__(backend)

def transpile_gate(self, gate):
energy_graph_i = self.backend.energy_level_graphs[gate._target_qudits]
QR = PhyQrDecomp(gate, energy_graph_i, not_stand_alone=False)
decomp, _algorithmic_cost, _total_cost = QR.execute()
return [op.dag() for op in reversed(decomp)]

def transpile(self, circuit):
self.circuit = circuit
instructions = circuit.instructions
new_instructions = []

for gate in instructions:
if gate.gate_type == GateTypes.SINGLE:
energy_graph_i = self.backend.energy_level_graphs[gate._target_qudits]
QR = PhyQrDecomp(gate, energy_graph_i, not_stand_alone=False)
decomp, _algorithmic_cost, _total_cost = QR.execute()
new_instructions += decomp
gate_trans = self.transpile_gate(gate)
gate_trans = [op.dag() for op in reversed(gate_trans)]
new_instructions.extend(gate_trans)
gc.collect()
else:
new_instructions.append(gate) # TODO REENCODING
new_instructions.append(gate)
transpiled_circuit = self.circuit.copy()
return transpiled_circuit.set_instructions(new_instructions)

Expand Down Expand Up @@ -65,7 +70,7 @@ def execute(self):
"VRz",
self.gate._target_qudits,
[self.graph.nodes[i]["lpmap"], thetaZ],
self.gate.dimension,
self.gate._dimensions,
) # (thetaZ, self.graph.nodes[i]['lpmap'], dimension)
decomp.append(phase_gate)
recover_dict[i] = thetaZ
Expand Down
Loading

0 comments on commit a285729

Please sign in to comment.