diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9bb79cd4..5a2bc9ba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -75,6 +75,7 @@ repos: additional_dependencies: - pytest - pandas-stubs + - types-requests # Also run Black on examples in the documentation - repo: https://github.com/adamchainz/blacken-docs diff --git a/pyproject.toml b/pyproject.toml index 16ac6cfa..801ee3ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,9 @@ dependencies = [ "matplotlib>=3.7; python_version < '3.12'", "matplotlib>=3.8; python_version >= '3.12'", "typing-extensions>=4.1", + "urllib3>=2.0.0", + "requests>=2.31.0", + "pytest>=8.3.4" ] dynamic = ["version"] diff --git a/src/mqt/qudits/compiler/__init__.py b/src/mqt/qudits/compiler/__init__.py index 0e183ae3..796ee97a 100644 --- a/src/mqt/qudits/compiler/__init__.py +++ b/src/mqt/qudits/compiler/__init__.py @@ -3,7 +3,4 @@ from .compiler_pass import CompilerPass from .dit_compiler import QuditCompiler -__all__ = [ - "CompilerPass", - "QuditCompiler", -] +__all__ = ["CompilerPass", "QuditCompiler"] diff --git a/src/mqt/qudits/compiler/compilation_minitools/local_compilation_minitools.py b/src/mqt/qudits/compiler/compilation_minitools/local_compilation_minitools.py index 6dcc746c..c043838d 100644 --- a/src/mqt/qudits/compiler/compilation_minitools/local_compilation_minitools.py +++ b/src/mqt/qudits/compiler/compilation_minitools/local_compilation_minitools.py @@ -11,6 +11,13 @@ T = TypeVar("T") +def check_lev(lev: int, dim: int) -> int: + if lev < dim: + return lev + msg = "Mapping Not Compatible with Circuit." + raise IndexError(msg) + + def swap_elements(list_nodes: list[T], i: int, j: int) -> list[T]: a = list_nodes[i] b = list_nodes[j] diff --git a/src/mqt/qudits/compiler/compilation_minitools/naive_unitary_verifier.py b/src/mqt/qudits/compiler/compilation_minitools/naive_unitary_verifier.py index 1c876647..8553f295 100755 --- a/src/mqt/qudits/compiler/compilation_minitools/naive_unitary_verifier.py +++ b/src/mqt/qudits/compiler/compilation_minitools/naive_unitary_verifier.py @@ -17,10 +17,19 @@ from mqt.qudits.quantum_circuit.gates import R, Rz, VirtRz -def mini_unitary_sim(circuit: QuantumCircuit, list_of_op: list[Gate]) -> NDArray[np.complex128, np.complex128]: +def permute_according_to_mapping(circuit: QuantumCircuit, mappings: list[list[int]]) -> NDArray: + lines = list(range(circuit.num_qudits)) + dimensions = circuit.dimensions + permutation = np.eye(dimensions[0])[:, mappings[0]] + for line in lines[1:]: + permutation = np.kron(permutation, np.eye(dimensions[line])[:, mappings[line]]) + return permutation + + +def mini_unitary_sim(circuit: QuantumCircuit) -> NDArray[np.complex128, np.complex128]: size = reduce(operator.mul, circuit.dimensions) id_mat = np.identity(size) - for gate in list_of_op: + for gate in circuit.instructions: id_mat = gate.to_matrix(identities=2) @ id_mat return id_mat @@ -34,17 +43,41 @@ def mini_sim(circuit: QuantumCircuit) -> NDArray[np.complex128]: return state -def phy_sdit_sim(circuit: QuantumCircuit) -> NDArray[np.complex128]: - assert circuit.mappings is not None - dim = circuit.dimensions[0] - permutation = np.eye(dim)[:, circuit.mappings[0]] - state = np.array(dim * [0.0 + 0.0j]) +def mini_phy_unitary_sim(circuit: QuantumCircuit) -> NDArray[np.complex128, np.complex128]: + assert circuit.final_mappings is not None + assert circuit.initial_mappings is not None + + dimensions = circuit.dimensions + size = reduce(operator.mul, dimensions) + id_mat = np.identity(size) + + final_permutation = permute_according_to_mapping(circuit, circuit.final_mappings) + init_permutation = permute_according_to_mapping(circuit, circuit.initial_mappings) + + id_mat = init_permutation @ id_mat + for gate in circuit.instructions: + id_mat = gate.to_matrix(identities=2) @ id_mat + + return final_permutation.T @ id_mat + + +def naive_phy_sim(circuit: QuantumCircuit) -> NDArray[np.complex128]: + assert circuit.final_mappings is not None + assert circuit.initial_mappings is not None + + dimensions = circuit.dimensions + state = np.array(np.prod(dimensions) * [0.0 + 0.0j]) state[0] = 1.0 + 0.0j - state = permutation @ state + + final_permutation = permute_according_to_mapping(circuit, circuit.final_mappings) + init_permutation = permute_according_to_mapping(circuit, circuit.initial_mappings) + + state = init_permutation @ state for gate in circuit.instructions: state = gate.to_matrix(identities=2) @ state - return state + + return final_permutation.T @ state class UnitaryVerifier: @@ -103,7 +136,9 @@ def verify(self) -> bool: if self.permutation_matrix_final is not None: target = np.linalg.inv(self.permutation_matrix_final) @ target + target.round(3) target /= target[0][0] + target.round(3) return bool((abs(target - np.identity(self.dimension, dtype="complex")) < 1e-4).all()) diff --git a/src/mqt/qudits/compiler/dit_compiler.py b/src/mqt/qudits/compiler/dit_compiler.py index c97d0cee..3a040280 100644 --- a/src/mqt/qudits/compiler/dit_compiler.py +++ b/src/mqt/qudits/compiler/dit_compiler.py @@ -3,12 +3,15 @@ import typing from typing import Optional -from ..core.lanes import Lanes +from ..core.custom_python_utils import append_to_front from ..quantum_circuit.components.extensions.gate_types import GateTypes +from . import CompilerPass +from .multidit.transpile.phy_multi_control_transp import PhyMultiSimplePass 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 +from .twodit.transpile.phy_two_control_transp import PhyEntSimplePass if typing.TYPE_CHECKING: from ..quantum_circuit import QuantumCircuit @@ -29,14 +32,24 @@ class QuditCompiler: "LogEntQRCEXPass": LogEntQRCEXPass, "PhyEntQRCEXPass": PhyEntQRCEXPass, "NaiveLocResynthOptPass": NaiveLocResynthOptPass, + "PhyEntSimplePass": PhyEntSimplePass, + "PhyMultiSimplePass": PhyMultiSimplePass, } def __init__(self) -> None: pass def compile(self, backend: Backend, circuit: QuantumCircuit, passes_names: list[str]) -> QuantumCircuit: + """Method compiles with passes chosen.""" + transpiled_circuit = circuit.copy() + final_mappings = [] + for i, graph in enumerate(backend.energy_level_graphs): + if i < circuit.num_qudits: + final_mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) + transpiled_circuit.set_final_mappings(final_mappings) + passes_dict = {} - new_instr = [] + new_instr: list[Gate] = [] # Instantiate and execute created classes for compiler_pass_name in passes_names: compiler_pass = self.passes_enabled[compiler_pass_name] @@ -47,76 +60,149 @@ def compile(self, backend: Backend, circuit: QuantumCircuit, passes_names: list[ passes_dict[GateTypes.TWO] = decomposition elif "Multi" in str(compiler_pass): passes_dict[GateTypes.MULTI] = decomposition - for gate in circuit.instructions: + + for gate in reversed(circuit.instructions): decomposer = typing.cast("Optional[CompilerPass]", passes_dict.get(gate.gate_type)) if decomposer is not None: new_instructions = decomposer.transpile_gate(gate) - new_instr.extend(new_instructions) + append_to_front(new_instr, new_instructions) else: - new_instr.append(gate) - - transpiled_circuit = circuit.copy() - mappings = [] + append_to_front(new_instr, gate) + initial_mappings = [] for i, graph in enumerate(backend.energy_level_graphs): if i < circuit.num_qudits: - mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) - transpiled_circuit.set_mapping(mappings) + initial_mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) + transpiled_circuit.set_initial_mappings(initial_mappings) return transpiled_circuit.set_instructions(new_instr) def compile_O0(self, backend: Backend, circuit: QuantumCircuit) -> QuantumCircuit: # noqa: N802 - passes = ["PhyLocQRPass", "PhyEntQRCEXPass"] + """Method compiles with PHY LOC QR and PHY ENT QR CEX with no optimization.""" + final_mappings = [] + for i, graph in enumerate(backend.energy_level_graphs): + if i < circuit.num_qudits: + final_mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) + + passes = ["PhyLocQRPass", "PhyEntQRCEXPass", "PhyMultiSimplePass"] compiled = self.compile(backend, circuit, passes) - mappings = [] + initial_mappings = [] for i, graph in enumerate(backend.energy_level_graphs): if i < circuit.num_qudits: - mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) - compiled.set_mapping(mappings) + initial_mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) + compiled.set_initial_mappings(initial_mappings) + compiled.set_final_mappings(final_mappings) return compiled @staticmethod - def compile_O1(backend: Backend, circuit: QuantumCircuit) -> QuantumCircuit: # noqa: N802 + def compile_O1_resynth(backend: Backend, circuit: QuantumCircuit) -> QuantumCircuit: # noqa: N802 + """Method compiles with PHY LOC QR and PHY ENT QR CEX with a resynth steps.""" phyloc = PhyLocQRPass(backend) phyent = PhyEntQRCEXPass(backend) resynth = NaiveLocResynthOptPass(backend) + phymulti = PhyMultiSimplePass(backend) + + transpiled_circuit = circuit.copy() + final_mappings = [] + for i, graph in enumerate(backend.energy_level_graphs): + if i < circuit.num_qudits: + final_mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) + transpiled_circuit.set_final_mappings(final_mappings) circuit = resynth.transpile(circuit) - new_instructions = [] - for gate in circuit.instructions: + new_instructions: list[Gate] = [] + for gate in reversed(circuit.instructions): ins: list[Gate] = [] if gate.gate_type is GateTypes.SINGLE: ins = phyloc.transpile_gate(gate) - new_instructions.extend(ins) - else: + append_to_front(new_instructions, ins) + elif gate.gate_type is GateTypes.TWO: ins = phyent.transpile_gate(gate) - new_instructions.extend(ins) - transpiled_circuit = circuit.copy() - mappings = [] + append_to_front(new_instructions, ins) + else: + ins = phymulti.transpile_gate(gate) + append_to_front(new_instructions, ins) + + initial_mappings = [] for i, graph in enumerate(backend.energy_level_graphs): if i < circuit.num_qudits: - mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) - transpiled_circuit.set_mapping(mappings) + initial_mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) + transpiled_circuit.set_initial_mappings(initial_mappings) return transpiled_circuit.set_instructions(new_instructions) @staticmethod - def compile_O2(backend: Backend, circuit: QuantumCircuit) -> QuantumCircuit: # noqa: N802 + def compile_O1_adaptive(backend: Backend, circuit: QuantumCircuit) -> QuantumCircuit: # noqa: N802 + """Method compiles with PHY LOC ADA and PHY ENT QR CEX.""" phyent = PhyEntQRCEXPass(backend) + phyloc = PhyLocAdaPass(backend) + phymulti = PhyMultiSimplePass(backend) + transpiled_circuit = circuit.copy() + final_mappings = [] + for i, graph in enumerate(backend.energy_level_graphs): + if i < circuit.num_qudits: + final_mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) - lanes = Lanes(circuit) - new_instructions = [] - for gate in circuit.instructions: + new_instructions: list[Gate] = [] + for gate in reversed(circuit.instructions): ins: list[Gate] = [] if gate.gate_type is GateTypes.SINGLE: - phyloc = PhyLocAdaPass(backend, lanes.next_is_local(gate)) ins = phyloc.transpile_gate(gate) - new_instructions.extend(ins) - else: + append_to_front(new_instructions, ins) + elif gate.gate_type is GateTypes.TWO: ins = phyent.transpile_gate(gate) - new_instructions.extend(ins) + append_to_front(new_instructions, ins) + else: + ins = phymulti.transpile_gate(gate) + append_to_front(new_instructions, ins) + + transpiled_circuit.set_instructions(new_instructions) + z_propagation_pass = ZPropagationOptPass(backend=backend, back=False) + transpiled_circuit = z_propagation_pass.transpile(transpiled_circuit) + + initial_mappings = [] + for i, graph in enumerate(backend.energy_level_graphs): + if i < circuit.num_qudits: + initial_mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) + transpiled_circuit.set_initial_mappings(initial_mappings) + transpiled_circuit.set_final_mappings(final_mappings) + + return transpiled_circuit + + @staticmethod + def compile_O2(backend: Backend, circuit: QuantumCircuit) -> QuantumCircuit: # noqa: N802 + """Method compiles with PHY LOC ADA and PHY ENT QR CEX with a resynth steps.""" + phyloc = PhyLocAdaPass(backend) + phyent = PhyEntQRCEXPass(backend) + resynth = NaiveLocResynthOptPass(backend) + phymulti = PhyMultiSimplePass(backend) transpiled_circuit = circuit.copy() - mappings = [] + + final_mappings = [] + for i, graph in enumerate(backend.energy_level_graphs): + if i < circuit.num_qudits: + final_mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) + + circuit = resynth.transpile(circuit) + new_instructions: list[Gate] = [] + for gate in reversed(circuit.instructions): + ins: list[Gate] = [] + if gate.gate_type is GateTypes.SINGLE: + ins = phyloc.transpile_gate(gate) + append_to_front(new_instructions, ins) + elif gate.gate_type is GateTypes.TWO: + ins = phyent.transpile_gate(gate) + append_to_front(new_instructions, ins) + else: + ins = phymulti.transpile_gate(gate) + append_to_front(new_instructions, ins) + + transpiled_circuit.set_instructions(new_instructions) + z_propagation_pass = ZPropagationOptPass(backend=backend, back=False) + transpiled_circuit = z_propagation_pass.transpile(transpiled_circuit) + + initial_mappings = [] for i, graph in enumerate(backend.energy_level_graphs): if i < circuit.num_qudits: - mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) - transpiled_circuit.set_mapping(mappings) + initial_mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) + transpiled_circuit.set_initial_mappings(initial_mappings) + transpiled_circuit.set_final_mappings(final_mappings) return transpiled_circuit.set_instructions(new_instructions) diff --git a/src/mqt/qudits/compiler/multidit/__init__.py b/src/mqt/qudits/compiler/multidit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/mqt/qudits/compiler/multidit/transpile/__init__.py b/src/mqt/qudits/compiler/multidit/transpile/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/mqt/qudits/compiler/multidit/transpile/phy_multi_control_transp.py b/src/mqt/qudits/compiler/multidit/transpile/phy_multi_control_transp.py new file mode 100644 index 00000000..743ed8e8 --- /dev/null +++ b/src/mqt/qudits/compiler/multidit/transpile/phy_multi_control_transp.py @@ -0,0 +1,186 @@ +from __future__ import annotations + +import gc +from typing import TYPE_CHECKING, cast + +import numpy as np + +from mqt.qudits.compiler import CompilerPass +from mqt.qudits.compiler.compilation_minitools.local_compilation_minitools import check_lev +from mqt.qudits.core.custom_python_utils import append_to_front +from mqt.qudits.quantum_circuit.components.extensions.controls import ControlData +from mqt.qudits.quantum_circuit.components.extensions.gate_types import GateTypes + +if TYPE_CHECKING: + from mqt.qudits.core import LevelGraph + from mqt.qudits.quantum_circuit import QuantumCircuit + from mqt.qudits.quantum_circuit.gate import Gate + from mqt.qudits.quantum_circuit.gates import R + from mqt.qudits.simulation.backends.backendv2 import Backend + + +class PhyMultiSimplePass(CompilerPass): + def __init__(self, backend: Backend) -> None: + super().__init__(backend) + from mqt.qudits.quantum_circuit import QuantumCircuit + + self.circuit = QuantumCircuit() + + def __routing(self, gate: R, graph: LevelGraph) -> tuple[list[R], R, list[R]]: + from mqt.qudits.compiler.onedit.local_operation_swap import cost_calculator, gate_chain_condition + from mqt.qudits.quantum_circuit.gates import R + + phi = gate.phi + _, pi_pulses_routing, temp_placement, _, _ = cost_calculator(gate, graph, 0) + + if temp_placement.nodes[gate.lev_a]["lpmap"] > temp_placement.nodes[gate.lev_b]["lpmap"]: + phi *= -1 + + physical_rotation = R( + self.circuit, + "R", + cast("int", gate.target_qudits), + [temp_placement.nodes[gate.lev_a]["lpmap"], temp_placement.nodes[gate.lev_b]["lpmap"], gate.theta, phi], + gate.dimensions, + ) + + physical_rotation = gate_chain_condition(pi_pulses_routing, physical_rotation) + pi_backs = [ + R( + self.circuit, + "R", + cast("int", gate.target_qudits), + [pi_g.lev_a, pi_g.lev_b, pi_g.theta, -pi_g.phi], + gate.dimensions, + ) + for pi_g in reversed(pi_pulses_routing) + ] + + return pi_pulses_routing, physical_rotation, pi_backs + + def transpile_gate(self, gate: Gate) -> list[Gate]: + from mqt.qudits.quantum_circuit.gates import R, Rz + + assert gate.gate_type == GateTypes.MULTI + self.circuit = gate.parent_circuit + + if isinstance(gate.target_qudits, int): + gate_controls = cast("ControlData", gate.control_info["controls"]) + indices = gate_controls.indices + states = gate_controls.ctrl_states + target_qudits = [*indices, gate.target_qudits] + dimensions = [gate.parent_circuit.dimensions[i] for i in target_qudits] + else: + target_qudits = gate.target_qudits + dimensions = cast("list[int]", gate.dimensions) + + # Get energy graphs for all control qudits and target qudit + energy_graphs = {qudit: self.backend.energy_level_graphs[qudit] for qudit in target_qudits} + + # Create logical-to-physical mapping for all qudits + lp_maps = { + qudit: [check_lev(lev, dim) for lev in energy_graphs[qudit].log_phy_map[:dim]] + for qudit, dim in zip(target_qudits, dimensions) + } + + if isinstance(gate, R): + gate_controls = cast("ControlData", gate.control_info["controls"]) + indices = gate_controls.indices + states = gate_controls.ctrl_states + if len(indices) > 0: + assert len(states) > 0 + assert len(states) == len(indices) + + # Create ghost rotation for routing + target_qudit = target_qudits[-1] # Last qudit is the target + ghost_rotation = R( + self.circuit, + f"R_ghost_t{target_qudit}", + target_qudit, + [gate.lev_a, gate.lev_b, gate.theta, gate.phi], + dimensions[-1], + None, + ) + + # Get routing operations + pi_pulses, rot, pi_backs = self.__routing(ghost_rotation, energy_graphs[target_qudit]) + + # Map all control levels to physical levels + new_ctrl_levels = [lp_maps[idx][state] for idx, state in zip(indices, states)] + + # Create new rotation with mapped control levels + new_parameters = [rot.lev_a, rot.lev_b, rot.theta, rot.phi] + newr = R( + self.circuit, + f"Rt{target_qudits}", + target_qudit, + new_parameters, + dimensions[-1], + ControlData(indices=indices, ctrl_states=new_ctrl_levels), + ) + + # Return the sequence of operations + return [*pi_pulses, newr, *pi_backs] + + if isinstance(gate, Rz): + gate_controls = cast("ControlData", gate.control_info["controls"]) + indices = gate_controls.indices + states = gate_controls.ctrl_states + if len(indices) > 0: + assert len(states) > 0 + assert len(states) == len(indices) + + # Create ghost rotation for routing + target_qudit = target_qudits[-1] # Last qudit is the target + ghost_rotation = R( + self.circuit, + f"R_ghost_t{target_qudit}", + target_qudit, + [gate.lev_a, gate.lev_b, gate.phi, np.pi / 2], + dimensions[-1], + None, + ) + + ghost_rotation.to_matrix() + + # Get routing operations + pi_pulses, rot, pi_backs = self.__routing(ghost_rotation, energy_graphs[target_qudit]) + + # Map all control levels to physical levels + new_ctrl_levels = [lp_maps[idx][state] for idx, state in zip(indices, states)] + + # Create new rotation with mapped control levels + new_parameters = [rot.lev_a, rot.lev_b, rot.theta] + if (rot.theta * rot.phi) * (gate.phi) < 0: + new_parameters = [rot.lev_a, rot.lev_b, -rot.theta] + newrz = Rz( + self.circuit, + f"Rt{target_qudits}", + target_qudit, + new_parameters, + dimensions[-1], + ControlData(indices=indices, ctrl_states=new_ctrl_levels), + ) + + # Return the sequence of operations + return [*pi_backs, newrz, *pi_pulses] + + msg = "The only MULTI gates supported for compilation at the moment are only multi-controlled R gates." + raise NotImplementedError(msg) + + def transpile(self, circuit: QuantumCircuit) -> QuantumCircuit: + self.circuit = circuit + instructions = circuit.instructions + new_instructions: list[Gate] = [] + + for gate in reversed(instructions): + if gate.gate_type == GateTypes.MULTI: + gate_trans = self.transpile_gate(gate) + append_to_front(new_instructions, gate_trans) + # new_instructions.extend(gate_trans) + gc.collect() + else: + append_to_front(new_instructions, gate) + # new_instructions.append(gate) + transpiled_circuit = self.circuit.copy() + return transpiled_circuit.set_instructions(new_instructions) diff --git a/src/mqt/qudits/compiler/naive_local_resynth/local_resynth.py b/src/mqt/qudits/compiler/naive_local_resynth/local_resynth.py index 5dc59318..6f48adf0 100644 --- a/src/mqt/qudits/compiler/naive_local_resynth/local_resynth.py +++ b/src/mqt/qudits/compiler/naive_local_resynth/local_resynth.py @@ -58,5 +58,5 @@ def transpile(self, circuit: QuantumCircuit) -> QuantumCircuit: for i, graph in enumerate(self.backend.energy_level_graphs): if i < circuit.num_qudits: mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) - transpiled_circuit.set_mapping(mappings) + transpiled_circuit.set_final_mappings(mappings) return transpiled_circuit.set_instructions(new_instructions) diff --git a/src/mqt/qudits/compiler/onedit/local_operation_swap/swap_routine.py b/src/mqt/qudits/compiler/onedit/local_operation_swap/swap_routine.py index 408ca64a..3186fe36 100644 --- a/src/mqt/qudits/compiler/onedit/local_operation_swap/swap_routine.py +++ b/src/mqt/qudits/compiler/onedit/local_operation_swap/swap_routine.py @@ -20,13 +20,18 @@ def find_logic_from_phys(lev_a: int, lev_b: int, graph: LevelGraph) -> list[int]: + counter = 0 # find node by physical level associated logic_nodes = [-1, -1] for node, node_data in graph.nodes(data=True): + if counter == 2: + break if node_data["lpmap"] == lev_a: logic_nodes[0] = node - if node_data["lpmap"] == lev_b: + counter += 1 + elif node_data["lpmap"] == lev_b: logic_nodes[1] = node + counter += 1 return logic_nodes diff --git a/src/mqt/qudits/compiler/onedit/local_phases_transpilation/propagate_virtrz.py b/src/mqt/qudits/compiler/onedit/local_phases_transpilation/propagate_virtrz.py index 04a6ceb0..559d5c1b 100644 --- a/src/mqt/qudits/compiler/onedit/local_phases_transpilation/propagate_virtrz.py +++ b/src/mqt/qudits/compiler/onedit/local_phases_transpilation/propagate_virtrz.py @@ -1,7 +1,6 @@ from __future__ import annotations -import copy -from typing import TYPE_CHECKING, Union, cast +from typing import TYPE_CHECKING, cast from ....quantum_circuit import gates from ... import CompilerPass @@ -24,14 +23,48 @@ def transpile_gate(gate: Gate) -> list[Gate]: return [gate] def transpile(self, circuit: QuantumCircuit) -> QuantumCircuit: - return self.remove_z(circuit, self.back) + from ....core.lanes import Lanes + from ....quantum_circuit.components.extensions.gate_types import GateTypes + + self.circuit = circuit + self.lanes = Lanes(self.circuit) + + final_mappings = [] + for i, graph in enumerate(self.backend.energy_level_graphs): + if i < circuit.num_qudits: + final_mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) + + for line in sorted(self.lanes.index_dict.keys()): + extracted_line: list[tuple[int, Gate]] = self.lanes.index_dict[line] + grouped_line: dict[int, list[list[tuple[int, Gate]]]] = self.lanes.find_consecutive_singles(extracted_line) + new_line: list[tuple[int, Gate]] = [] + for group in grouped_line[line]: + if group[0][1].gate_type == GateTypes.SINGLE: + new_line.extend(self.propagate_z(circuit, group, self.back)) + else: + new_line.append(group[0]) + + self.lanes.index_dict[line] = new_line + + new_instructions = self.lanes.extract_instructions() + transpiled_circuit = circuit.copy() + initial_mappings = [] + for i, graph in enumerate(self.backend.energy_level_graphs): + if i < circuit.num_qudits: + initial_mappings.append([lev for lev in graph.log_phy_map if lev < circuit.dimensions[i]]) + transpiled_circuit.set_initial_mappings(initial_mappings) + transpiled_circuit.set_final_mappings(final_mappings) + + return transpiled_circuit.set_instructions(new_instructions) @staticmethod - def propagate_z(circuit: QuantumCircuit, line: list[R | VirtRz], back: bool) -> tuple[list[R], list[VirtRz]]: + def propagate_z(circuit: QuantumCircuit, group: list[tuple[int, Gate]], back: bool) -> list[tuple[int, R | VirtRz]]: + tag = group[0][0] + line = [couple[1] for couple in group] z_angles: dict[int, float] = {} list_of_x_yrots: list[R] = [] qudit_index = cast("int", line[0].target_qudits) - dimension = line[0].dimensions + dimension = cast("int", line[0].dimensions) for i in range(dimension): z_angles[i] = 0.0 @@ -43,8 +76,6 @@ def propagate_z(circuit: QuantumCircuit, line: list[R | VirtRz], back: bool) -> for gate_index in range(len(line)): if isinstance(line[gate_index], R): - # line[gate_index].lev_b - # object is R if back: new_phi = pi_mod( line[gate_index].phi + z_angles[line[gate_index].lev_a] - z_angles[line[gate_index].lev_b] @@ -62,7 +93,7 @@ def propagate_z(circuit: QuantumCircuit, line: list[R | VirtRz], back: bool) -> dimension, ) ) - elif isinstance(line[gate_index], VirtRz): # except AttributeError: + elif isinstance(line[gate_index], VirtRz): z_angles[line[gate_index].lev_a] = pi_mod(z_angles[line[gate_index].lev_a] + line[gate_index].phi) if back: list_of_x_yrots.reverse() @@ -71,10 +102,10 @@ def propagate_z(circuit: QuantumCircuit, line: list[R | VirtRz], back: bool) -> zseq.extend([ gates.VirtRz(circuit, "VRz", qudit_index, [e_lev, z_angles[e_lev]], dimension) for e_lev in z_angles ]) - # Zseq.append(Rz(Z_angles[e_lev], e_lev, QC.dimension)) - - return list_of_x_yrots, zseq + combined_seq = zseq + list_of_x_yrots if back else list_of_x_yrots + zseq + return [(tag, gate) for gate in combined_seq] + """ @staticmethod def find_intervals_with_same_target_qudits(instructions: list[Gate]) -> list[tuple[int, ...]]: intervals: list[tuple[int, ...]] = [] @@ -111,13 +142,17 @@ def remove_z(self, original_circuit: QuantumCircuit, back: bool = True) -> Quant for interval in intervals: if len(interval) > 1: + from ....quantum_circuit.gates import R, VirtRz + + sequence = cast(list[Union[R, VirtRz]], circuit.instructions[interval[0]: interval[-1] + 1]) sequence = cast("list[Union[R, VirtRz]]", circuit.instructions[interval[0] : interval[-1] + 1]) fixed_seq: list[R] = [] z_tail: list[VirtRz] = [] - fixed_seq, z_tail = self.propagate_z(circuit, sequence, back) + combined_seq = self.propagate_z(circuit, sequence, back) - combined_seq = z_tail + fixed_seq if back else fixed_seq + z_tail - new_instructions[interval[0] : interval[-1] + 1] = [] + # combined_seq = z_tail + fixed_seq if back else fixed_seq + z_tail + new_instructions[interval[0]: interval[-1] + 1] = [] new_instructions.extend(combined_seq) return circuit.set_instructions(new_instructions) + """ diff --git a/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_adaptive_decomp.py b/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_adaptive_decomp.py index b44d5fca..e31c6744 100644 --- a/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_adaptive_decomp.py +++ b/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_adaptive_decomp.py @@ -3,11 +3,13 @@ import contextlib import gc import itertools +from random import shuffle from typing import TYPE_CHECKING, cast import numpy as np from ....core import NAryTree +from ....core.custom_python_utils import append_to_front from ....exceptions import SequenceFoundError from ....quantum_circuit import gates from ....quantum_circuit.components.extensions.gate_types import GateTypes @@ -56,15 +58,15 @@ def transpile_gate(self, gate: Gate) -> list[Gate]: def transpile(self, circuit: QuantumCircuit) -> QuantumCircuit: self.circuit: QuantumCircuit = circuit instructions: list[Gate] = circuit.instructions - new_instructions = [] + new_instructions: list[Gate] = [] - for gate in instructions: + for gate in reversed(instructions): if gate.gate_type == GateTypes.SINGLE: gate_trans = self.transpile_gate(gate) - new_instructions.extend(gate_trans) + append_to_front(new_instructions, gate_trans) gc.collect() else: - new_instructions.append(gate) + append_to_front(new_instructions, gate) transpiled_circuit = self.circuit.copy() return transpiled_circuit.set_instructions(new_instructions) @@ -111,10 +113,6 @@ def execute(self) -> tuple[list[Gate], tuple[float, float], LevelGraph]: matrices_decomposed_m, final_graph = self.z_extraction( matrices_decomposed, final_graph, self.phase_propagation ) - else: - pass - - self.TREE.print_tree(self.TREE.root, "TREE: ") return matrices_decomposed_m, best_cost, final_graph @@ -129,7 +127,7 @@ def z_extraction( matrices = [*matrices, d.rotation] u_ = decomposition[-1].u_of_level # take U of last elaboration which should be the diagonal matrix found - + u_.round(4) # check if close to diagonal ucopy = u_.copy() @@ -157,13 +155,14 @@ def z_extraction( placement.nodes[i]["phase_storage"] = new_mod(placement.nodes[i]["phase_storage"]) else: phy_n_i = placement.nodes[i]["lpmap"] - + angle_phy = new_mod(np.angle(diag_u[i])) phase_gate = gates.VirtRz( - self.circuit, "VRz", self.qudit_index, [phy_n_i, np.angle(diag_u[i])], self.dimension - ) # old version: VirtRz(np.angle(diag_U[i]), phy_n_i, - # dimension) - - u_ = phase_gate.to_matrix(identities=0) @ u_ # matmul(phase_gate.to_matrix(identities=0), U_) + self.circuit, "VRz", self.qudit_index, [phy_n_i, angle_phy], self.dimension + ) # old version: VirtRz(np.angle(diag_U[i]), phy_n_i, dimension) + log_phase_gate = gates.VirtRz(self.circuit, "VRz", self.qudit_index, [i, angle_phy], self.dimension) + log_phase_gate.to_matrix(identities=0) + u_ = log_phase_gate.to_matrix(identities=0) @ u_ + u_.round(4) matrices.append(phase_gate) @@ -224,7 +223,7 @@ def dfs(self, current_root: TreeNode, level: int = 0) -> None: ) # R(theta, phi, r, r2, dimension) u_temp = rotation_involved.to_matrix(identities=0) @ u_ # matmul(rotation_involved.matrix, U_) - + u_temp.round(4) non_zeros = np.count_nonzero(abs(u_temp) > 1.0e-4) ( @@ -281,6 +280,30 @@ def dfs(self, current_root: TreeNode, level: int = 0) -> None: pi_pulses_routing, ) + def calculate_sparsity(matrix: NDArray) -> float: + total_elements = matrix.size + non_zero_elements = np.count_nonzero(np.abs(matrix) > 1e-8) # np.count_nonzero(matrix) + return cast("float", non_zero_elements / total_elements) + + def change_kids(lst: list[TreeNode]) -> list[TreeNode]: + # Check if the list is non-empty + if not lst: + return lst + vals = [calculate_sparsity(obj.u_of_level) for obj in lst] + # Calculate the range of values in the list + min_val, _max_val = min(vals), max(vals) + min_from_one = 1 - min_val + dim = lst[0].u_of_level.shape[0] + threshold = (dim - 3) ** 2 / dim**2 + # If the spread is below the threshold, shuffle the list + if min_from_one < threshold and len(lst) > dim**2 * 0.6: + shuffle(lst) + else: + lst = sorted(lst, key=lambda obj: calculate_sparsity(obj.u_of_level)) + + return lst + if current_root.children is not None: - for child in current_root.children: + new_kids = change_kids(current_root.children) + for child in new_kids: self.dfs(child, level + 1) diff --git a/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_qr_decomp.py b/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_qr_decomp.py index 8bdda773..f6d4071b 100644 --- a/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_qr_decomp.py +++ b/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_qr_decomp.py @@ -1,5 +1,6 @@ from __future__ import annotations +import copy import gc from typing import TYPE_CHECKING, cast @@ -54,7 +55,7 @@ def __init__(self, gate: Gate, graph_orig: LevelGraph, z_prop: bool = False, not self.dimension: int = cast("int", gate.dimensions) self.qudit_index: int = cast("int", gate.target_qudits) self.U: NDArray = gate.to_matrix(identities=0) - self.graph: LevelGraph = graph_orig + self.graph: LevelGraph = copy.deepcopy(graph_orig) self.phase_propagation: bool = z_prop self.not_stand_alone: bool = not_stand_alone diff --git a/src/mqt/qudits/compiler/onedit/randomized_benchmarking/__init__.py b/src/mqt/qudits/compiler/onedit/randomized_benchmarking/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/mqt/qudits/compiler/onedit/randomized_benchmarking/bench_suite.py b/src/mqt/qudits/compiler/onedit/randomized_benchmarking/bench_suite.py new file mode 100644 index 00000000..efe14446 --- /dev/null +++ b/src/mqt/qudits/compiler/onedit/randomized_benchmarking/bench_suite.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import itertools +import pickle # noqa: S403 +import typing +from pathlib import Path + +import numpy as np + +from mqt.qudits.quantum_circuit import QuantumCircuit + +if typing.TYPE_CHECKING: + from numpy.typing import NDArray + + +# Define H and S gates for a specific qudit dimension +def get_h_gate(dim: int) -> NDArray: + circuit_d = QuantumCircuit(1, [dim], 0) + h = circuit_d.h(0) + return h.to_matrix() + + +def get_s_gate(dim: int) -> NDArray: + circuit_d = QuantumCircuit(1, [dim], 0) + s = circuit_d.s(0) + return s.to_matrix() + + +def matrix_hash(matrix: NDArray) -> int: + """Hash a numpy matrix using its byte representation.""" + return hash(matrix.tobytes()) + + +def generate_clifford_group(d: int, max_length: int = 5) -> dict[str, NDArray]: + # Initialize H and S gates + h_gate = get_h_gate(d) + s_gate = get_s_gate(d) + + gates = {"h": h_gate, "s": s_gate} + clifford_group: dict[str, NDArray] = {} + hash_table = set() # To store matrix hashes for fast lookups + + # Iterate over different combination lengths + for length in range(1, max_length + 1): + for seq in itertools.product("hs", repeat=length): + seq_str = "".join(seq) + gate_product = np.eye(d) + + # Multiply gates in the sequence + for gate in seq: + gate_product = np.dot(gates[gate], gate_product) + + # Hash the matrix + matrix_h = matrix_hash(gate_product) + + # Check if this matrix is already in the group using the hash table + if matrix_h not in hash_table: + clifford_group[seq_str] = gate_product + hash_table.add(matrix_h) + + return clifford_group + + +def get_package_data_path(filename: str) -> Path: + """Get the path to the data directory within the package.""" + current_dir = Path(__file__).parent + data_dir = current_dir / "data" + data_dir.mkdir(exist_ok=True) + return data_dir / filename + + +def save_clifford_group_to_file(clifford_group: dict[str, NDArray], filename: str) -> None: + """Save the Clifford group to the 'data' directory in the current package.""" + filepath = get_package_data_path(filename) + filepath.write_bytes(pickle.dumps(clifford_group)) + + +def load_clifford_group_from_file(filename: str) -> dict[str, NDArray] | None: + """Load the Clifford group from the 'data' directory in the current package.""" + filepath = get_package_data_path(filename) + if filepath.exists(): + return typing.cast("dict[str, NDArray]", pickle.loads(filepath.read_bytes())) # noqa: S301 + return None diff --git a/src/mqt/qudits/compiler/onedit/randomized_benchmarking/data/cliffords_3.dat b/src/mqt/qudits/compiler/onedit/randomized_benchmarking/data/cliffords_3.dat new file mode 100644 index 00000000..327cbcfd Binary files /dev/null and b/src/mqt/qudits/compiler/onedit/randomized_benchmarking/data/cliffords_3.dat differ diff --git a/src/mqt/qudits/compiler/state_compilation/state_preparation.py b/src/mqt/qudits/compiler/state_compilation/state_preparation.py index cfaa50b3..41239f63 100644 --- a/src/mqt/qudits/compiler/state_compilation/state_preparation.py +++ b/src/mqt/qudits/compiler/state_compilation/state_preparation.py @@ -100,9 +100,16 @@ def __str__(self) -> str: class StatePrep: - def __init__(self, quantum_circuit: QuantumCircuit, state: NDArray[np.complex128], approx: bool = False) -> None: + def __init__( + self, + quantum_circuit: QuantumCircuit, + state: NDArray[np.complex128], + not_noisy: bool = True, + approx: bool = False, + ) -> None: self.circuit = quantum_circuit self.state = state + self.not_noisy = not_noisy self.approximation = approx def retrieve_local_sequence( @@ -186,8 +193,12 @@ def compile_state(self) -> QuantumCircuit: nodes = op.get_control_nodes() levels = op.get_control_levels() if op.is_z(): - new_circuit.rz(op.qudit, [0, 1, op.theta]).control(nodes, levels) + rz = new_circuit.rz(op.qudit, [0, 1, op.theta]).control(nodes, levels) + if self.not_noisy: + rz.turn_off_noise() else: - new_circuit.r(op.qudit, [op.levels[0], op.levels[1], op.theta, op.phi]).control(nodes, levels) + r = new_circuit.r(op.qudit, [op.levels[0], op.levels[1], op.theta, op.phi]).control(nodes, levels) + if self.not_noisy: + r.turn_off_noise() return new_circuit diff --git a/src/mqt/qudits/compiler/twodit/blocks/crot.py b/src/mqt/qudits/compiler/twodit/blocks/crot.py index 969713be..847990ca 100755 --- a/src/mqt/qudits/compiler/twodit/blocks/crot.py +++ b/src/mqt/qudits/compiler/twodit/blocks/crot.py @@ -42,17 +42,7 @@ def crot_101_as_list(self, theta: float, phi: float) -> list[Gate]: frame_there = gates.R(self.circuit, "R", index_target, [0, 1, np.pi / 2, -phi - np.pi / 2], dim_target) # on1(R(np.pi / 2, -phi - np.pi / 2, 0, 1, d).matrix, d) - if CEX_SEQUENCE is None: - cex = gates.CEx( - self.circuit, - "CEx" + str([self.circuit.dimensions[i] for i in self.indices]), - self.indices, - None, - [self.circuit.dimensions[i] for i in self.indices], - None, - ) - # Cex().cex_101(d, 0) - else: + if CEX_SEQUENCE is not None: cex_s = CEX_SEQUENCE ############# @@ -60,14 +50,32 @@ def crot_101_as_list(self, theta: float, phi: float) -> list[Gate]: compose: list[Gate] = [frame_there] if CEX_SEQUENCE is None: - compose.append(cex) + compose.append( + gates.CEx( + self.circuit, + "CEx" + str([self.circuit.dimensions[i] for i in self.indices]), + self.indices, + None, + [self.circuit.dimensions[i] for i in self.indices], + None, + ) + ) else: compose += cex_s compose.append(single_excitation) compose.append(tminus) if CEX_SEQUENCE is None: - compose.append(cex) + compose.append( + gates.CEx( + self.circuit, + "CEx" + str([self.circuit.dimensions[i] for i in self.indices]), + self.indices, + None, + [self.circuit.dimensions[i] for i in self.indices], + None, + ) + ) else: compose += cex_s @@ -112,8 +120,8 @@ def permute_crot_101_as_list(self, i: int, theta: float, phase: float) -> list[G perm = [permute_there_10, permute_there_11] # matmul(permute_there_10, permute_there_11) perm_back = [permute_there_11_dag, permute_there_10_dag] # perm.conj().T - rot_there += perm - rot_back += perm_back + rot_there = perm + rot_back = perm_back if q0_i != 1: permute_there_00 = gates.R(self.circuit, "R", index_ctrl, [1, q0_i, np.pi, -np.pi / 2], dim_ctrl) diff --git a/src/mqt/qudits/compiler/twodit/blocks/pswap.py b/src/mqt/qudits/compiler/twodit/blocks/pswap.py index 108fc321..c45cdb13 100755 --- a/src/mqt/qudits/compiler/twodit/blocks/pswap.py +++ b/src/mqt/qudits/compiler/twodit/blocks/pswap.py @@ -28,11 +28,20 @@ def pswap_101_as_list_phases(self, theta: float, phi: float) -> list[Gate]: if dim_target == 2: theta = -theta - h_0 = gates.Rh(self.circuit, "Rh", index_ctrl, [0, 1], dim_ctrl) - h_1 = gates.Rh(self.circuit, "Rh", index_target, [0, 1], dim_target) + # replicated gate because has to be used several times in the decomposition although the same + h_0_0 = gates.Rh(self.circuit, "Rh", index_ctrl, [0, 1], dim_ctrl) + h_0_1 = gates.Rh(self.circuit, "Rh", index_ctrl, [0, 1], dim_ctrl) + h_0_2 = gates.Rh(self.circuit, "Rh", index_ctrl, [0, 1], dim_ctrl) + h_0_3 = gates.Rh(self.circuit, "Rh", index_ctrl, [0, 1], dim_ctrl) + + h_1_0 = gates.Rh(self.circuit, "Rh", index_target, [0, 1], dim_target) + h_1_1 = gates.Rh(self.circuit, "Rh", index_target, [0, 1], dim_target) + h_1_2 = gates.Rh(self.circuit, "Rh", index_target, [0, 1], dim_target) + h_1_3 = gates.Rh(self.circuit, "Rh", index_target, [0, 1], dim_target) # HditR(0, 1, d).matrix - zpiov2_0 = gates.Rz(self.circuit, "Rz-zpiov2", index_ctrl, [0, 1, np.pi / 2], dim_ctrl) + zpiov2_0_0 = gates.Rz(self.circuit, "Rz-zpiov2", index_ctrl, [0, 1, np.pi / 2], dim_ctrl) + zpiov2_0_1 = gates.Rz(self.circuit, "Rz-zpiov2", index_ctrl, [0, 1, np.pi / 2], dim_ctrl) # ZditR(np.pi / 2, 0, 1, d).matrix zp_0 = gates.Rz(self.circuit, "Rz-zp", index_ctrl, [0, 1, np.pi], dim_ctrl) @@ -54,17 +63,7 @@ def pswap_101_as_list_phases(self, theta: float, phi: float) -> list[Gate]: tplus = gates.Rz(self.circuit, "Rz", index_target, [0, 1, +theta / 2], dim_target) # on1(ZditR(theta / 2, 0, 1, d).matrix, d) - if CEX_SEQUENCE is None: - cex = gates.CEx( - self.circuit, - "CEx" + str([self.circuit.dimensions[i] for i in self.indices]), - self.indices, - None, - [self.circuit.dimensions[i] for i in self.indices], - None, - ) - # Cex().cex_101(d, 0) - else: + if CEX_SEQUENCE is not None: cex_s = CEX_SEQUENCE ############################################################################################# @@ -94,15 +93,24 @@ def pswap_101_as_list_phases(self, theta: float, phi: float) -> list[Gate]: compose.append(h_1) # (on1(h_, d)) compose.append(zpiov2_0) # (on0(zpiov2, d))""" - compose.extend((h_0, h_1, zpiov2_0)) + compose.extend((h_0_0, h_1_0, zpiov2_0_0)) if CEX_SEQUENCE is None: - compose.append(cex) + compose.append( + gates.CEx( + self.circuit, + "CEx" + str([self.circuit.dimensions[i] for i in self.indices]), + self.indices, + None, + [self.circuit.dimensions[i] for i in self.indices], + None, + ) + ) else: compose += cex_s - compose.append(h_0) # (on0(h_, d)) - compose.append(h_1) # (on1(h_, d)) + compose.append(h_0_1) # (on0(h_, d)) + compose.append(h_1_1) # (on1(h_, d)) compose.append(zp_0) # (on0(zp, d)) @@ -111,14 +119,32 @@ def pswap_101_as_list_phases(self, theta: float, phi: float) -> list[Gate]: compose.append(rphi_there_1) # (on1(rphi_there, d)) # ---------- if CEX_SEQUENCE is None: - compose.append(cex) + compose.append( + gates.CEx( + self.circuit, + "CEx" + str([self.circuit.dimensions[i] for i in self.indices]), + self.indices, + None, + [self.circuit.dimensions[i] for i in self.indices], + None, + ) + ) else: compose += cex_s compose.append(single_excitation) compose.append(tminus) if CEX_SEQUENCE is None: - compose.append(cex) + compose.append( + gates.CEx( + self.circuit, + "CEx" + str([self.circuit.dimensions[i] for i in self.indices]), + self.indices, + None, + [self.circuit.dimensions[i] for i in self.indices], + None, + ) + ) else: compose += cex_s @@ -129,18 +155,27 @@ def pswap_101_as_list_phases(self, theta: float, phi: float) -> list[Gate]: ################################## - compose.append(h_0) - compose.append(h_1) # (on1(h_, d)) + compose.append(h_0_2) + compose.append(h_1_2) # (on1(h_, d)) - compose.append(zpiov2_0) # (on0(zpiov2, d)) + compose.append(zpiov2_0_1) # (on0(zpiov2, d)) if CEX_SEQUENCE is None: - compose.append(cex) + compose.append( + gates.CEx( + self.circuit, + "CEx" + str([self.circuit.dimensions[i] for i in self.indices]), + self.indices, + None, + [self.circuit.dimensions[i] for i in self.indices], + None, + ) + ) else: compose += cex_s - compose.append(h_0) # (on0(h_, d)) - compose.append(h_1) # (on1(h_, d)) + compose.append(h_0_3) # (on0(h_, d)) + compose.append(h_1_3) # (on1(h_, d)) if dim_target != 2: r_flip_back_1 = gates.R( @@ -151,8 +186,12 @@ def pswap_101_as_list_phases(self, theta: float, phi: float) -> list[Gate]: return compose def pswap_101_as_list_no_phases(self, theta: float, phi: float) -> list[Gate]: - rotation = self.pswap_101_as_list_phases(-theta / 4, phi) - return rotation + rotation + rotation + rotation + return ( + self.pswap_101_as_list_phases(-theta / 4, phi) + + self.pswap_101_as_list_phases(-theta / 4, phi) + + self.pswap_101_as_list_phases(-theta / 4, phi) + + self.pswap_101_as_list_phases(-theta / 4, phi) + ) def permute_pswap_101_as_list(self, pos: int, theta: float, phase: float, with_phase: bool = False) -> list[Gate]: index_ctrl = self.indices[0] diff --git a/src/mqt/qudits/compiler/twodit/entanglement_qr/log_ent_qr_cex_decomp.py b/src/mqt/qudits/compiler/twodit/entanglement_qr/log_ent_qr_cex_decomp.py index fe135620..0078f88c 100755 --- a/src/mqt/qudits/compiler/twodit/entanglement_qr/log_ent_qr_cex_decomp.py +++ b/src/mqt/qudits/compiler/twodit/entanglement_qr/log_ent_qr_cex_decomp.py @@ -154,10 +154,17 @@ def execute(self) -> tuple[list[Gate], int, int]: gate_matrix = r___.to_matrix() u_ = gate_matrix @ u_ - u_.round(3) ####################### decomp += sequence_rotation_involved + global_phase = u_[0][0] + from mqt.qudits.quantum_circuit.gates import VirtRz + + for lev in range(self.dimensions[1]): + gatez = VirtRz( + self.circuit, "VirtRzGlobal", self.qudit_indices[1], [lev, np.angle(global_phase)], self.dimensions[1] + ) + decomp.append(gatez) self.decomposition = decomp return decomp, crot_counter, pswap_counter diff --git a/src/mqt/qudits/compiler/twodit/entanglement_qr/phy_ent_qr_cex_decomp.py b/src/mqt/qudits/compiler/twodit/entanglement_qr/phy_ent_qr_cex_decomp.py index 345086e2..715fcf3b 100644 --- a/src/mqt/qudits/compiler/twodit/entanglement_qr/phy_ent_qr_cex_decomp.py +++ b/src/mqt/qudits/compiler/twodit/entanglement_qr/phy_ent_qr_cex_decomp.py @@ -4,10 +4,9 @@ from typing import TYPE_CHECKING, cast from mqt.qudits.compiler import CompilerPass -from mqt.qudits.compiler.onedit import PhyLocQRPass from mqt.qudits.compiler.twodit.entanglement_qr import EntangledQRCEX +from mqt.qudits.core.custom_python_utils import append_to_front from mqt.qudits.quantum_circuit.components.extensions.gate_types import GateTypes -from mqt.qudits.quantum_circuit.gates import CEx, Perm if TYPE_CHECKING: from mqt.qudits.quantum_circuit import QuantumCircuit @@ -22,59 +21,63 @@ def __init__(self, backend: Backend) -> None: self.circuit = QuantumCircuit() - def transpile_gate(self, gate: Gate) -> list[Gate]: - target_qudits = cast("list[int]", gate.target_qudits) - dimensions = cast("list[int]", gate.dimensions) + def __transpile_local_ops(self, gate: Gate) -> list[Gate]: + from mqt.qudits.compiler.onedit.mapping_aware_transpilation import PhyQrDecomp - energy_graph_c = self.backend.energy_level_graphs[target_qudits[0]] - energy_graph_t = self.backend.energy_level_graphs[target_qudits[1]] + energy_graph_i = self.backend.energy_level_graphs[cast("int", gate.target_qudits)] + qr = PhyQrDecomp(gate, energy_graph_i, not_stand_alone=False) + decomp, _algorithmic_cost, _total_cost = qr.execute() + return decomp - lp_map_0 = [lev for lev in energy_graph_c.log_phy_map if lev < dimensions[0]] - lp_map_1 = [lev for lev in energy_graph_t.log_phy_map if lev < dimensions[1]] + @staticmethod + def __transpile_two_ops(backend: Backend, gate: Gate) -> tuple[bool, list[Gate]]: + assert gate.gate_type == GateTypes.TWO + from mqt.qudits.compiler.twodit.transpile.phy_two_control_transp import PhyEntSimplePass - if isinstance(gate, CEx): - parent_circ = gate.parent_circuit - new_ctrl_lev = lp_map_0[gate.ctrl_lev] - new_la = lp_map_1[gate.lev_a] - new_lb = lp_map_1[gate.lev_b] - if new_la < new_lb: - new_parameters = [new_la, new_lb, new_ctrl_lev, gate.phi] - else: - new_parameters = [new_lb, new_la, new_ctrl_lev, gate.phi] - tcex = CEx(parent_circ, "CEx_t" + str(target_qudits), target_qudits, new_parameters, dimensions, None) - return [tcex] - - perm_0 = Perm(gate.parent_circuit, "Pm_ent_0", target_qudits[0], lp_map_0, dimensions[0]) - perm_1 = Perm(gate.parent_circuit, "Pm_ent_1", target_qudits[1], lp_map_1, dimensions[1]) - perm_0_dag = Perm(gate.parent_circuit, "Pm_ent_0", target_qudits[0], lp_map_0, dimensions[0]).dag() - perm_1_dag = Perm(gate.parent_circuit, "Pm_ent_1", target_qudits[1], lp_map_1, dimensions[1]).dag() - - phyloc = PhyLocQRPass(self.backend) - perm_0_seq = phyloc.transpile_gate(perm_0) - perm_1_seq = phyloc.transpile_gate(perm_1) - perm_0_d_seq = phyloc.transpile_gate(perm_0_dag) - perm_1_d_seq = phyloc.transpile_gate(perm_1_dag) - - eqr = EntangledQRCEX(gate) + phy_two_simple = PhyEntSimplePass(backend) + transpiled = phy_two_simple.transpile_gate(gate) + return (len(transpiled) > 0), transpiled + + def transpile_gate(self, orig_gate: Gate) -> list[Gate]: + simple_gate, simple_gate_decomp = self.__transpile_two_ops(self.backend, orig_gate) + if simple_gate: + return simple_gate_decomp + + eqr = EntangledQRCEX(orig_gate) decomp, _countcr, _countpsw = eqr.execute() - perm_0_d_seq.extend(perm_1_d_seq) - perm_0_d_seq.extend(decomp) - perm_0_d_seq.extend(perm_0_seq) - perm_0_d_seq.extend(perm_1_seq) - return [op.dag() for op in reversed(decomp)] + # Full sequence of logical operations to be implemented to reconstruct + # the logical operation on the device + full_logical_sequence = [op.dag() for op in reversed(decomp)] + + # Actual implementation of the gate in the device based on the mapping + physical_sequence: list[Gate] = [] + for gate in reversed(full_logical_sequence): + if gate.gate_type == GateTypes.SINGLE: + loc_gate = self.__transpile_local_ops(gate) + append_to_front(physical_sequence, [op.dag() for op in reversed(loc_gate)]) + elif gate.gate_type == GateTypes.TWO: + _, ent_gate = self.__transpile_two_ops(self.backend, gate) + append_to_front(physical_sequence, ent_gate) + elif gate.gate_type == GateTypes.MULTI: + msg = "Multi not supposed to be in decomposition!" + raise RuntimeError(msg) + + return physical_sequence def transpile(self, circuit: QuantumCircuit) -> QuantumCircuit: self.circuit = circuit instructions = circuit.instructions - new_instructions = [] + new_instructions: list[Gate] = [] - for gate in instructions: + for gate in reversed(instructions): if gate.gate_type == GateTypes.TWO: gate_trans = self.transpile_gate(gate) - new_instructions.extend(gate_trans) + append_to_front(new_instructions, gate_trans) + # new_instructions.extend(gate_trans) gc.collect() else: - new_instructions.append(gate) + append_to_front(new_instructions, gate) + # new_instructions.append(gate) transpiled_circuit = self.circuit.copy() return transpiled_circuit.set_instructions(new_instructions) diff --git a/src/mqt/qudits/compiler/twodit/transpile/__init__.py b/src/mqt/qudits/compiler/twodit/transpile/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/mqt/qudits/compiler/twodit/transpile/phy_two_control_transp.py b/src/mqt/qudits/compiler/twodit/transpile/phy_two_control_transp.py new file mode 100644 index 00000000..79be05c6 --- /dev/null +++ b/src/mqt/qudits/compiler/twodit/transpile/phy_two_control_transp.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +import gc +from typing import TYPE_CHECKING, cast + +import numpy as np + +from mqt.qudits.compiler import CompilerPass +from mqt.qudits.compiler.compilation_minitools.local_compilation_minitools import check_lev +from mqt.qudits.core.custom_python_utils import append_to_front +from mqt.qudits.quantum_circuit.components.extensions.controls import ControlData +from mqt.qudits.quantum_circuit.components.extensions.gate_types import GateTypes + +if TYPE_CHECKING: + from mqt.qudits.core import LevelGraph + from mqt.qudits.quantum_circuit import QuantumCircuit + from mqt.qudits.quantum_circuit.gate import Gate + from mqt.qudits.quantum_circuit.gates import R + from mqt.qudits.simulation.backends.backendv2 import Backend + + +class PhyEntSimplePass(CompilerPass): + def __init__(self, backend: Backend) -> None: + super().__init__(backend) + from mqt.qudits.quantum_circuit import QuantumCircuit + + self.circuit = QuantumCircuit() + + def __routing(self, gate: R, graph: LevelGraph) -> tuple[list[R], R, list[R]]: + from mqt.qudits.compiler.onedit.local_operation_swap import cost_calculator, gate_chain_condition + from mqt.qudits.quantum_circuit.gates import R + + phi = gate.phi + _, pi_pulses_routing, temp_placement, _, _ = cost_calculator(gate, graph, 0) + + if temp_placement.nodes[gate.lev_a]["lpmap"] > temp_placement.nodes[gate.lev_b]["lpmap"]: + phi *= -1 + + physical_rotation = R( + self.circuit, + "R", + cast("int", gate.target_qudits), + [temp_placement.nodes[gate.lev_a]["lpmap"], temp_placement.nodes[gate.lev_b]["lpmap"], gate.theta, phi], + gate.dimensions, + ) + + physical_rotation = gate_chain_condition(pi_pulses_routing, physical_rotation) + pi_backs = [ + R( + self.circuit, + "R", + cast("int", gate.target_qudits), + [pi_g.lev_a, pi_g.lev_b, pi_g.theta, -pi_g.phi], + gate.dimensions, + ) + for pi_g in reversed(pi_pulses_routing) + ] + + return pi_pulses_routing, physical_rotation, pi_backs + + def transpile_gate(self, gate: Gate) -> list[Gate]: + assert gate.gate_type == GateTypes.TWO + from mqt.qudits.quantum_circuit.gates import CEx, R, Rz + + self.circuit = gate.parent_circuit + + if isinstance(gate.target_qudits, int) and isinstance(gate, (R, Rz)): + gate_controls = cast("ControlData", gate.control_info["controls"]) + indices = gate_controls.indices + states = gate_controls.ctrl_states + target_qudits = [*indices, gate.target_qudits] + dimensions = [gate.parent_circuit.dimensions[i] for i in target_qudits] + else: + target_qudits = cast("list[int]", gate.target_qudits) + dimensions = cast("list[int]", gate.dimensions) + + energy_graph_c = self.backend.energy_level_graphs[target_qudits[0]] + energy_graph_t = self.backend.energy_level_graphs[target_qudits[1]] + lp_map_0 = [check_lev(lev, dimensions[0]) for lev in energy_graph_c.log_phy_map[: dimensions[0]]] + + if isinstance(gate, CEx): + phi = gate.phi + ghost_rotation = R( + self.circuit, + "R_cex_t" + str(target_qudits[1]), + target_qudits[1], + [gate.lev_a, gate.lev_b, np.pi, phi], + dimensions[1], + None, + ) + pi_pulses, rot, pi_backs = self.__routing(ghost_rotation, energy_graph_t) + new_ctrl_lev = lp_map_0[gate.ctrl_lev] + new_parameters = [rot.lev_a, rot.lev_b, new_ctrl_lev, rot.phi] + tcex = CEx(self.circuit, "CEx_t" + str(target_qudits), target_qudits, new_parameters, dimensions, None) + return [*pi_pulses, tcex, *pi_backs] + if isinstance(gate, R): + gate_controls = cast("ControlData", gate.control_info["controls"]) + indices = gate_controls.indices + states = gate_controls.ctrl_states + if len(indices) == 1: + assert len(states) == 1 + ghost_rotation = R( + self.circuit, + "R_ghost_t" + str(target_qudits[1]), + target_qudits[1], + [gate.lev_a, gate.lev_b, gate.theta, gate.phi], + dimensions[1], + None, + ) + pi_pulses, rot, pi_backs = self.__routing(ghost_rotation, energy_graph_t) + new_ctrl_lev = lp_map_0[states[0]] + new_parameters = [rot.lev_a, rot.lev_b, rot.theta, rot.phi] + newr = R( + self.circuit, + "Rt" + str(target_qudits), + target_qudits[1], + new_parameters, + dimensions[1], + ControlData(indices=indices, ctrl_states=[new_ctrl_lev]), + ) + return [*pi_pulses, newr, *pi_backs] + elif isinstance(gate, Rz): + gate_controls = cast("ControlData", gate.control_info["controls"]) + indices = gate_controls.indices + states = gate_controls.ctrl_states + if len(indices) == 1: + assert len(states) == 1 + ghost_rotation = R( + self.circuit, + "R_ghost_t" + str(target_qudits[1]), + target_qudits[1], + [gate.lev_a, gate.lev_b, gate.phi, np.pi], + dimensions[1], + None, + ) + pi_pulses, rot, pi_backs = self.__routing(ghost_rotation, energy_graph_t) + new_ctrl_lev = lp_map_0[states[0]] + new_parameters = [rot.lev_a, rot.lev_b, rot.theta] + if (rot.theta * rot.phi) * (gate.phi) < 0: + new_parameters = [rot.lev_a, rot.lev_b, -rot.theta] + newrz = Rz( + self.circuit, + "Rzt" + str(target_qudits), + target_qudits[1], + new_parameters, + dimensions[1], + ControlData(indices=indices, ctrl_states=[new_ctrl_lev]), + ) + return [*pi_backs, newrz, *pi_pulses] + return [] + + def transpile(self, circuit: QuantumCircuit) -> QuantumCircuit: + self.circuit = circuit + instructions = circuit.instructions + new_instructions: list[Gate] = [] + + for gate in reversed(instructions): + if gate.gate_type == GateTypes.MULTI: + gate_trans = self.transpile_gate(gate) + append_to_front(new_instructions, gate_trans) + # new_instructions.extend(gate_trans) + gc.collect() + else: + append_to_front(new_instructions, gate) + # new_instructions.append(gate) + transpiled_circuit = self.circuit.copy() + return transpiled_circuit.set_instructions(new_instructions) diff --git a/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/sparsifier.py b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/sparsifier.py index e5c1ae64..8c4092cd 100644 --- a/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/sparsifier.py +++ b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/sparsifier.py @@ -7,6 +7,7 @@ import numpy as np from scipy.optimize import minimize # type: ignore[import-not-found] +from scipy.stats import unitary_group # type: ignore[import-not-found] from mqt.qudits.compiler.compilation_minitools import gate_expand_to_circuit from mqt.qudits.compiler.twodit.variational_twodit_compilation.opt import Optimizer @@ -20,6 +21,56 @@ from mqt.qudits.quantum_circuit.gate import Gate +def random_unitary_matrix(n: int) -> NDArray[np.complex128, np.complex128]: + return unitary_group.rvs(n) + + +def random_sparse_unitary(n: int, density: float = 0.4) -> NDArray: + """Generate a random sparse-like complex unitary matrix as a numpy array. + + Parameters: + ----------- + n : int + Size of the matrix (n x n) + density : float + Approximate density of non-zero elements (between 0 and 1) + + Returns: + -------- + numpy.ndarray + A complex unitary matrix with approximate sparsity + """ + # Create a random complex matrix with mostly zeros + mat_a = np.zeros((n, n), dtype=complex) + + # Calculate number of non-zero elements + nnz = int(density * n * n) + + rng = np.random.default_rng() + # Generate random positions for non-zero elements + positions = rng.choice(n * n, size=nnz, replace=False) + rows, cols = np.unravel_index(positions, (n, n)) + + values = (rng.standard_normal(nnz) + 1j * rng.standard_normal(nnz)) * np.sqrt(n) + mat_a[rows, cols] = values + + # Add a small random perturbation to all elements to avoid pure identity results + perturbation = (rng.standard_normal((n, n)) + 1j * rng.standard_normal((n, n))) * 0.01 + mat_a += perturbation + + # Perform QR decomposition to get unitary matrix + q, _r = np.linalg.qr(mat_a) + + # Make Q more sparse by zeroing out small elements + mask = np.abs(q) < np.sqrt(density) # Adaptive threshold + q[mask] = 0 + + # Ensure unitarity by performing another QR + q_, _ = np.linalg.qr(q) + + return q_ + + def apply_rotations( m: NDArray[np.complex128, np.complex128], params_list: list[float], dims: list[int] ) -> NDArray[np.complex128, np.complex128]: diff --git a/src/mqt/qudits/core/custom_python_utils.py b/src/mqt/qudits/core/custom_python_utils.py new file mode 100644 index 00000000..6b8764d1 --- /dev/null +++ b/src/mqt/qudits/core/custom_python_utils.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from typing import TypeVar + +T = TypeVar("T") # Generic type + + +def append_to_front(lst: list[T], elements: T | list[T]) -> None: + """Appends either a single element or a list of elements to the front of the list.""" + if isinstance(elements, list): + # Extend the list at the front using slicing for multiple elements + lst[:0] = elements + else: + # Insert a single element at the front + lst.insert(0, elements) diff --git a/src/mqt/qudits/core/lanes.py b/src/mqt/qudits/core/lanes.py index d409527d..4b76cc97 100644 --- a/src/mqt/qudits/core/lanes.py +++ b/src/mqt/qudits/core/lanes.py @@ -46,7 +46,7 @@ def create_lanes(self) -> CircuitView: self.index_dict[index] = [] self.index_dict[index].append(gate_tuple) elif gate.gate_type in {GateTypes.TWO, GateTypes.MULTI}: - indices: list[int] = cast("list[int]", gate.target_qudits) + indices = gate.reference_lines for index in indices: if index not in self.index_dict: self.index_dict[index] = [] @@ -90,7 +90,7 @@ def find_consecutive_singles(self, gates: LineView | None = None) -> CircuitGrou else: consecutive_groups[target_qudits] = [[gate_tuple]] else: - qudits_targeted: list[int] = cast("list[int]", gate.target_qudits) + qudits_targeted = gate.reference_lines for qudit in qudits_targeted: consecutive_groups[qudit].append([gate_tuple]) consecutive_groups[qudit].append([]) diff --git a/src/mqt/qudits/quantum_circuit/circuit.py b/src/mqt/qudits/quantum_circuit/circuit.py index df4d0cb4..19184d28 100644 --- a/src/mqt/qudits/quantum_circuit/circuit.py +++ b/src/mqt/qudits/quantum_circuit/circuit.py @@ -86,6 +86,9 @@ class QuantumCircuit: "s": "s", "x": "x", "z": "z", + "noisex": "noisex", + "noisey": "noisey", + "noisez": "noisez", } def __init__(self, *args: int | QuantumRegister | list[int] | None) -> None: @@ -100,7 +103,8 @@ def __init__(self, *args: int | QuantumRegister | list[int] | None) -> None: self.num_cl: int = 0 self._num_qudits: int = 0 self._dimensions: list[int] = [] - self.mappings: list[list[int]] | None = None + self.final_mappings: list[list[int]] | None = None + self.initial_mappings: list[list[int]] | None = None self.path_save: str | None = None if len(args) == 0: @@ -157,10 +161,14 @@ def append(self, qreg: QuantumRegister) -> None: self._dimensions += qreg.dimensions num_lines_stored = len(self.sitemap) - for i in range(qreg.size): - qreg.local_sitemap[i] = num_lines_stored + i - self.sitemap[str(qreg.label), i] = (num_lines_stored + i, qreg.dimensions[i]) - self.inverse_sitemap[num_lines_stored + i] = (str(qreg.label), i) + try: + for i in range(qreg.size): + qreg.local_sitemap[i] = num_lines_stored + i + self.sitemap[str(qreg.label), i] = (num_lines_stored + i, qreg.dimensions[i]) + self.inverse_sitemap[num_lines_stored + i] = (str(qreg.label), i) + except IndexError: + msg = "Check your Quantum Register to have the right number of lines and number of dimensions" + raise IndexError(msg) from None def append_classic(self, creg: ClassicRegister) -> None: self.classic_registers.append(creg) @@ -310,8 +318,12 @@ def set_instructions(self, sequence: Sequence[Gate]) -> QuantumCircuit: self.number_gates = len(sequence) return self - def set_mapping(self, mappings: list[list[int]]) -> QuantumCircuit: - self.mappings = mappings + def set_final_mappings(self, mappings: list[list[int]]) -> QuantumCircuit: + self.final_mappings = mappings + return self + + def set_initial_mappings(self, mappings: list[list[int]]) -> QuantumCircuit: + self.initial_mappings = mappings return self def from_qasm(self, qasm_prog: str) -> None: @@ -348,16 +360,18 @@ def from_qasm(self, qasm_prog: str) -> None: qudits_call = [t[0] for t in list(tuples_qudits)] if is_not_none_or_empty(op["params"]): if op["controls"]: - function(qudits_call, op["params"], op["controls"]) + gate = function(qudits_call, op["params"], op["controls"]) else: - function(qudits_call, op["params"]) + gate = function(qudits_call, op["params"]) elif op["controls"]: - function(qudits_call, op["controls"]) + gate = function(qudits_call, op["controls"]) else: - function(qudits_call) + gate = function(qudits_call) else: msg = "the required gate_matrix is not available anymore." raise NotImplementedError(msg) + if op["dagger"]: + gate.dag() def to_qasm(self) -> str: text = "" @@ -368,7 +382,9 @@ def to_qasm(self) -> str: text += f"creg meas[{len(self.dimensions)}];\n" for op in self.instructions: - text += op.__qasm__() + if op.to_qasm() is None: + pass + text += op.to_qasm() cregs_indices = iter(list(range(len(self.dimensions)))) for qreg in self.quantum_registers: @@ -435,7 +451,25 @@ def compileO0(self, backend_name: str) -> QuantumCircuit: # noqa: N802 return qudit_compiler.compile_O0(backend_ion, self) - def compileO1(self, backend_name: str) -> QuantumCircuit: # noqa: N802 + def compileO1(self, backend_name: str, mode: str = "resynth") -> QuantumCircuit: # noqa: N802 + from mqt.qudits.compiler import QuditCompiler + from mqt.qudits.simulation import MQTQuditProvider + + qudit_compiler = QuditCompiler() + provider = MQTQuditProvider() + backend_ion = provider.get_backend(backend_name) + + if mode == "adapt": + new_circuit = qudit_compiler.compile_O1_adaptive(backend_ion, self) + elif mode == "resynth": + new_circuit = qudit_compiler.compile_O1_resynth(backend_ion, self) + else: + msg = f"mode {mode} not supported" + raise ValueError(msg) + + return new_circuit + + def compileO2(self, backend_name: str) -> QuantumCircuit: # noqa: N802 from mqt.qudits.compiler import QuditCompiler from mqt.qudits.simulation import MQTQuditProvider @@ -443,7 +477,7 @@ def compileO1(self, backend_name: str) -> QuantumCircuit: # noqa: N802 provider = MQTQuditProvider() backend_ion = provider.get_backend(backend_name) - return qudit_compiler.compile_O1(backend_ion, self) + return qudit_compiler.compile_O2(backend_ion, self) def set_initial_state(self, state: ArrayLike, approx: bool = False) -> QuantumCircuit: from mqt.qudits.compiler.state_compilation.state_preparation import StatePrep diff --git a/src/mqt/qudits/quantum_circuit/components/extensions/gate_types.py b/src/mqt/qudits/quantum_circuit/components/extensions/gate_types.py index 74a3e139..8bc58736 100644 --- a/src/mqt/qudits/quantum_circuit/components/extensions/gate_types.py +++ b/src/mqt/qudits/quantum_circuit/components/extensions/gate_types.py @@ -6,9 +6,9 @@ class GateTypes(enum.Enum): """Enumeration for gate types.""" - SINGLE: str = "Single Qudit Gate" - TWO: str = "Two Qudit Gate" - MULTI: str = "Multi Qudit Gate" + SINGLE = "Single Qudit Gate" + TWO = "Two Qudit Gate" + MULTI = "Multi Qudit Gate" CORE_GATE_TYPES: tuple[GateTypes, GateTypes, GateTypes] = (GateTypes.SINGLE, GateTypes.TWO, GateTypes.MULTI) diff --git a/src/mqt/qudits/quantum_circuit/components/extensions/matrix_factory.py b/src/mqt/qudits/quantum_circuit/components/extensions/matrix_factory.py index 6c6df839..c21d84b9 100644 --- a/src/mqt/qudits/quantum_circuit/components/extensions/matrix_factory.py +++ b/src/mqt/qudits/quantum_circuit/components/extensions/matrix_factory.py @@ -21,8 +21,6 @@ def __init__(self, gate: Gate, identities_flag: int) -> None: def generate_matrix(self) -> NDArray[np.complex128]: matrix = self.gate.__array__() - if self.gate.dagger: - matrix = matrix.conj().T control_info = typing.cast("typing.Optional[ControlData]", self.gate.control_info["controls"]) lines = self.gate.reference_lines.copy() diff --git a/src/mqt/qudits/quantum_circuit/gate.py b/src/mqt/qudits/quantum_circuit/gate.py index bfe3e35c..9a35dd3e 100644 --- a/src/mqt/qudits/quantum_circuit/gate.py +++ b/src/mqt/qudits/quantum_circuit/gate.py @@ -65,6 +65,7 @@ def __init__( self.theta: float = theta self.phi: float = phi self.qasm_tag = qasm_tag + self.is_susceptible = True @property def reference_lines(self) -> list[int]: @@ -77,15 +78,26 @@ def reference_lines(self) -> list[int]: if len(lines) == 0: msg = "Gate has no target or control lines" raise CircuitError(msg) + return lines @abstractmethod def __array__(self) -> NDArray: # noqa: PLW3201 pass + def update_params(self, params: Parameter) -> None: + self._params = params + + def _dagger_properties(self) -> None: + pass + def dag(self) -> Gate: - self._name += "_dag" - self.dagger = True + if self.dagger: + self._name = self._name.replace("_dag", "") + else: + self._name += "_dag" + self.dagger = not self.dagger + self._dagger_properties() return self def to_matrix(self, identities: int = 0) -> NDArray: @@ -112,18 +124,18 @@ def control(self, indices: list[int], ctrl_states: list[int]) -> Gate: if len(indices) > self.parent_circuit.num_qudits or any( idx >= self.parent_circuit.num_qudits for idx in indices ): - msg = "Indices or Number of Controls is beyond the Quantum Circuit Size" + msg = "Indices or Number of Controls is beyond the Quantum Circuit Size " raise IndexError(msg) if isinstance(self.target_qudits, int): if self.target_qudits in indices: - msg = "Controls overlap with targets" + msg = "Controls overlap with targets " raise IndexError(msg) elif any(idx in list(self.target_qudits) for idx in indices): msg = "Controls overlap with targets" raise IndexError(msg) - # if isinstance(self._dimensions, int): - # dimensions = [self._dimensions] - if any(ctrl >= self.parent_circuit.dimensions[i] for i, ctrl in enumerate(ctrl_states)): + + control_dimensions = [self.parent_circuit.dimensions[ind] for ind in indices] + if any(ctrl >= control_dimensions[i] for i, ctrl in enumerate(ctrl_states)): msg = "Controls States beyond qudit size " raise IndexError(msg) self._controls_data = ControlData(indices, ctrl_states) @@ -132,7 +144,7 @@ def control(self, indices: list[int], ctrl_states: list[int]) -> Gate: self.set_gate_type_two() elif len(self.reference_lines) > 2: self.set_gate_type_multi() - self.check_long_range() + self.is_long_range = self.check_long_range() return self def validate_parameter(self, param: Parameter) -> bool: # noqa: PLR6301 ARG002 @@ -185,6 +197,9 @@ def __qasm__(self) -> str: # noqa: PLW3201 return string + ";\n" + def to_qasm(self) -> str: + return self.__qasm__() + def check_long_range(self) -> bool: target_qudits: list[int] = self.reference_lines if len(target_qudits) > 1: @@ -223,3 +238,6 @@ def return_custom_data(self) -> str: file_path = Path(self.parent_circuit.path_save) / f"{self._name}_{key}.npy" np.save(file_path, self._params) return f"({file_path}) " + + def turn_off_noise(self) -> None: + self.is_susceptible = False diff --git a/src/mqt/qudits/quantum_circuit/gates/csum.py b/src/mqt/qudits/quantum_circuit/gates/csum.py index 9086ea83..2f0a1f8d 100644 --- a/src/mqt/qudits/quantum_circuit/gates/csum.py +++ b/src/mqt/qudits/quantum_circuit/gates/csum.py @@ -54,4 +54,14 @@ def __array__(self) -> NDArray[np.complex128, np.complex128]: # noqa: PLW3201 matrix += np.kron(mapmat, x_mat_i) else: matrix += np.kron(x_mat_i, mapmat) + + if self.dagger: + return matrix.conj().T + return matrix + + def to_qasm(self) -> str: + string_description = self.__qasm__() + if self.dagger: + return "inv @ " + string_description + return string_description diff --git a/src/mqt/qudits/quantum_circuit/gates/custom_multi.py b/src/mqt/qudits/quantum_circuit/gates/custom_multi.py index bc9fbceb..52cd35c1 100644 --- a/src/mqt/qudits/quantum_circuit/gates/custom_multi.py +++ b/src/mqt/qudits/quantum_circuit/gates/custom_multi.py @@ -12,6 +12,7 @@ from ..circuit import QuantumCircuit from ..components.extensions.controls import ControlData + from ..gate import Parameter class CustomMulti(Gate): @@ -30,7 +31,7 @@ def __init__( circuit=circuit, name=name, gate_type=GateTypes.MULTI, - target_qudits=target_qudits, + target_qudits=sorted(target_qudits), dimensions=dimensions, control_set=controls, params=parameters, @@ -44,10 +45,14 @@ def __array__(self) -> NDArray: # noqa: PLW3201 return self.__array_storage @staticmethod - def validate_parameter(parameter: NDArray | None = None) -> bool: + def validate_parameter(parameter: Parameter) -> bool: if parameter is None: return True # or False, depending on whether None is considered valid return bool( isinstance(parameter, np.ndarray) and (parameter.dtype == np.complex128 or np.issubdtype(parameter.dtype, np.number)) ) + + def _dagger_properties(self) -> None: + self.__array_storage = self.__array_storage.conj().T + self.update_params(self.__array_storage) diff --git a/src/mqt/qudits/quantum_circuit/gates/custom_one.py b/src/mqt/qudits/quantum_circuit/gates/custom_one.py index 684669d7..2fd292dc 100644 --- a/src/mqt/qudits/quantum_circuit/gates/custom_one.py +++ b/src/mqt/qudits/quantum_circuit/gates/custom_one.py @@ -12,6 +12,7 @@ from ..circuit import QuantumCircuit from ..components.extensions.controls import ControlData + from ..gate import Parameter class CustomOne(Gate): @@ -44,10 +45,14 @@ def __array__(self) -> NDArray: # noqa: PLW3201 return self.__array_storage @staticmethod - def validate_parameter(parameter: NDArray | None = None) -> bool: + def validate_parameter(parameter: Parameter) -> bool: if parameter is None: return True # or False, depending on whether None is considered valid return bool( isinstance(parameter, np.ndarray) and (parameter.dtype == np.complex128 or np.issubdtype(parameter.dtype, np.number)) ) + + def _dagger_properties(self) -> None: + self.__array_storage = self.__array_storage.conj().T + self.update_params(self.__array_storage) diff --git a/src/mqt/qudits/quantum_circuit/gates/custom_two.py b/src/mqt/qudits/quantum_circuit/gates/custom_two.py index 5dda4972..a6e7dc46 100644 --- a/src/mqt/qudits/quantum_circuit/gates/custom_two.py +++ b/src/mqt/qudits/quantum_circuit/gates/custom_two.py @@ -12,6 +12,7 @@ from ..circuit import QuantumCircuit from ..components.extensions.controls import ControlData + from ..gate import Parameter class CustomTwo(Gate): @@ -30,7 +31,7 @@ def __init__( circuit=circuit, name=name, gate_type=GateTypes.TWO, - target_qudits=target_qudits, + target_qudits=sorted(target_qudits), dimensions=dimensions, control_set=controls, params=parameters, @@ -44,10 +45,14 @@ def __array__(self) -> NDArray: # noqa: PLW3201 return self.__array_storage @staticmethod - def validate_parameter(parameter: NDArray | None = None) -> bool: + def validate_parameter(parameter: Parameter) -> bool: if parameter is None: return True # or False, depending on whether None is considered valid return bool( isinstance(parameter, np.ndarray) and (parameter.dtype == np.complex128 or np.issubdtype(parameter.dtype, np.number)) ) + + def _dagger_properties(self) -> None: + self.__array_storage = self.__array_storage.conj().T + self.update_params(self.__array_storage) diff --git a/src/mqt/qudits/quantum_circuit/gates/cx.py b/src/mqt/qudits/quantum_circuit/gates/cx.py index ab23b463..8a3eea7c 100644 --- a/src/mqt/qudits/quantum_circuit/gates/cx.py +++ b/src/mqt/qudits/quantum_circuit/gates/cx.py @@ -54,7 +54,7 @@ def __array__(self) -> NDArray: # noqa: PLW3201 levels_swap_low: int = cast("int", self._params[0]) levels_swap_high: int = cast("int", self._params[1]) ctrl_level: int = cast("int", self._params[2]) - ang: float = cast("float", self._params[3]) + ang: float = self.phi dimension = reduce(operator.mul, self.dimensions) dimension_ctrl, dimension_target = self.dimensions qudits_targeted = cast("list[int]", self.target_qudits) @@ -83,6 +83,10 @@ def __array__(self) -> NDArray: # noqa: PLW3201 return result + def _dagger_properties(self) -> None: + self.phi += np.pi + self.update_params([self._params[0], self._params[1], self._params[2], self.phi]) + @staticmethod def validate_parameter(parameter: Parameter) -> bool: if parameter is None: @@ -96,7 +100,7 @@ def validate_parameter(parameter: Parameter) -> bool: assert 0 <= parameter[0] < parameter[1], ( f"lev_a and lev_b are out of range or in wrong order: {parameter[0]}, {parameter[1]}" ) - assert 0 <= parameter[3] <= 2 * np.pi, f"Angle should be in the range [0, 2*pi]: {parameter[2]}" + # assert 0 <= parameter[3] <= 2 * np.pi, f"Angle should be in the range [0, 2*pi]: {parameter[2]}" return True diff --git a/src/mqt/qudits/quantum_circuit/gates/h.py b/src/mqt/qudits/quantum_circuit/gates/h.py index cdf57875..6419c86c 100644 --- a/src/mqt/qudits/quantum_circuit/gates/h.py +++ b/src/mqt/qudits/quantum_circuit/gates/h.py @@ -45,9 +45,19 @@ def __array__(self) -> NDArray: # noqa: PLW3201 array0[e0] = 1 array1[e1] = 1 matrix += omega * np.outer(array0, array1) - return matrix * (1 / np.sqrt(self.dimensions)) + + matrix *= 1 / np.sqrt(self.dimensions) + if self.dagger: + return matrix.conj().T + return matrix @property def dimensions(self) -> int: assert isinstance(self._dimensions, int), "Dimensions must be an integer" return self._dimensions + + def to_qasm(self) -> str: + string_description = self.__qasm__() + if self.dagger: + return "inv @ " + string_description + return string_description diff --git a/src/mqt/qudits/quantum_circuit/gates/ls.py b/src/mqt/qudits/quantum_circuit/gates/ls.py index e3324c75..92d0c8b8 100644 --- a/src/mqt/qudits/quantum_circuit/gates/ls.py +++ b/src/mqt/qudits/quantum_circuit/gates/ls.py @@ -58,13 +58,19 @@ def __array__(self) -> NDArray: # noqa: PLW3201 return expm(-1j * self.theta * exp_matrix) + def _dagger_properties(self) -> None: + self.theta *= -1 + self.update_params([self.theta]) + @staticmethod def validate_parameter(param: Parameter) -> bool: if param is None: return False if isinstance(param, list): - assert 0 <= cast("float", param[0]) <= 2 * np.pi, f"Angle should be in the range [0, 2*pi]: {param[0]}" + """assert -2 * np.pi <= cast(float, param[0]) <= 2 * np.pi, ( + f"Angle should be in the range [-2*pi, 2*pi]: {param[0]}" + )""" return True if isinstance(param, np.ndarray): diff --git a/src/mqt/qudits/quantum_circuit/gates/ms.py b/src/mqt/qudits/quantum_circuit/gates/ms.py index 5ac02b5c..5848b272 100644 --- a/src/mqt/qudits/quantum_circuit/gates/ms.py +++ b/src/mqt/qudits/quantum_circuit/gates/ms.py @@ -67,15 +67,19 @@ def __array__(self) -> NDArray: # noqa: PLW3201 ) return expm(-1j * theta * gate_part_1 @ gate_part_2 / 4) + def _dagger_properties(self) -> None: + self.theta *= -1 + self.update_params([self.theta]) + @staticmethod def validate_parameter(parameter: Parameter) -> bool: if parameter is None: return False if isinstance(parameter, list): - assert 0 <= cast("float", parameter[0]) <= 2 * np.pi, ( - f"Angle should be in the range [0, 2*pi]: {parameter[0]}" - ) + """assert -2 * np.pi <= cast(float, parameter[0]) <= 2 * np.pi, ( + f"Angle should be in the range [-2*pi, 2*pi]: {parameter[0]}" + )""" return True if isinstance(parameter, np.ndarray): # Add validation for numpy array if needed diff --git a/src/mqt/qudits/quantum_circuit/gates/noise_x.py b/src/mqt/qudits/quantum_circuit/gates/noise_x.py index 9cdef08e..ebe938be 100644 --- a/src/mqt/qudits/quantum_circuit/gates/noise_x.py +++ b/src/mqt/qudits/quantum_circuit/gates/noise_x.py @@ -86,3 +86,9 @@ def validate_parameter(self, parameter: Parameter) -> bool: def dimensions(self) -> int: assert isinstance(self._dimensions, int), "Dimensions must be an integer" return self._dimensions + + def to_qasm(self) -> str: + string_description = self.__qasm__() + if self.dagger: + return "inv @ " + string_description + return string_description diff --git a/src/mqt/qudits/quantum_circuit/gates/noise_y.py b/src/mqt/qudits/quantum_circuit/gates/noise_y.py index afb67d95..0782e9b2 100644 --- a/src/mqt/qudits/quantum_circuit/gates/noise_y.py +++ b/src/mqt/qudits/quantum_circuit/gates/noise_y.py @@ -86,3 +86,9 @@ def validate_parameter(self, parameter: Parameter) -> bool: def dimensions(self) -> int: assert isinstance(self._dimensions, int), "Dimensions must be an integer" return self._dimensions + + def to_qasm(self) -> str: + string_description = self.__qasm__() + if self.dagger: + return "inv @ " + string_description + return string_description diff --git a/src/mqt/qudits/quantum_circuit/gates/perm.py b/src/mqt/qudits/quantum_circuit/gates/perm.py index d4821952..c0c94ded 100644 --- a/src/mqt/qudits/quantum_circuit/gates/perm.py +++ b/src/mqt/qudits/quantum_circuit/gates/perm.py @@ -42,6 +42,11 @@ def __init__( def __array__(self) -> NDArray: # noqa: PLW3201 return np.eye(self.dimensions)[:, self.perm_data] + def _dagger_properties(self) -> None: + dagm = self.__array__().T + self.perm_data = np.argmax(dagm, axis=0) + self.update_params(self.perm_data) + def validate_parameter(self, parameter: Parameter) -> bool: if parameter is None: return False diff --git a/src/mqt/qudits/quantum_circuit/gates/r.py b/src/mqt/qudits/quantum_circuit/gates/r.py index 3e880c9f..1f183507 100644 --- a/src/mqt/qudits/quantum_circuit/gates/r.py +++ b/src/mqt/qudits/quantum_circuit/gates/r.py @@ -67,6 +67,10 @@ def __array__(self) -> NDArray: # noqa: PLW3201 * GellMann(self.parent_circuit, "Gellman_s", qudit_targeted, ps, self.dimensions, None).to_matrix() ) + def _dagger_properties(self) -> None: + self.theta *= -1 + self.update_params([self.lev_a, self.lev_b, self.theta, self.phi]) + @staticmethod def levels_setter(la: int, lb: int) -> tuple[int, int]: if la < lb: @@ -83,9 +87,17 @@ def validate_parameter(self, parameter: Parameter) -> bool: assert isinstance(parameter[2], float) assert isinstance(parameter[3], float) assert parameter[0] >= 0 - assert parameter[0] < self.dimensions + assert parameter[0] < self.dimensions, ( + "Choice of levels is non-physical, or " + "logic states require routing through an ancilla state in the " + "energy-level-graph, due to long-range interactions." + ) assert parameter[1] >= 0 - assert parameter[1] < self.dimensions + assert parameter[1] < self.dimensions, ( + "Choice of levels is non-physical, or " + "logic states require routing through an ancilla state in the " + "energy-level-graph, due to long-range interactions." + ) assert parameter[0] != parameter[1] # Useful to remember direction of the rotation self.original_lev_a = parameter[0] diff --git a/src/mqt/qudits/quantum_circuit/gates/rh.py b/src/mqt/qudits/quantum_circuit/gates/rh.py index 25baddc0..e3912598 100644 --- a/src/mqt/qudits/quantum_circuit/gates/rh.py +++ b/src/mqt/qudits/quantum_circuit/gates/rh.py @@ -57,7 +57,10 @@ def __array__(self) -> NDArray: # noqa: PLW3201 dimension, ).to_matrix() - return np.matmul(pi_x, rotate) + matrix = np.matmul(pi_x, rotate) + if self.dagger: + return matrix.conj().T + return matrix @staticmethod def levels_setter(la: int, lb: int) -> tuple[int, int]: @@ -93,3 +96,9 @@ def validate_parameter(self, parameter: Parameter) -> bool: def dimensions(self) -> int: assert isinstance(self._dimensions, int), "Dimensions must be an integer" return self._dimensions + + def to_qasm(self) -> str: + string_description = self.__qasm__() + if self.dagger: + return "inv @ " + string_description + return string_description diff --git a/src/mqt/qudits/quantum_circuit/gates/rz.py b/src/mqt/qudits/quantum_circuit/gates/rz.py index 9dc47efb..c537a756 100644 --- a/src/mqt/qudits/quantum_circuit/gates/rz.py +++ b/src/mqt/qudits/quantum_circuit/gates/rz.py @@ -61,6 +61,10 @@ def __array__(self) -> NDArray[np.complex128, np.complex128]: # noqa: PLW3201 return np.matmul(np.matmul(pi_back, rotate), pi_there) # pi_back @ rotate @ pi_there + def _dagger_properties(self) -> None: + self.phi *= -1 + self.update_params([self.lev_a, self.lev_b, self.phi]) + @staticmethod def levels_setter(la: int, lb: int) -> tuple[int, int]: if la < lb: diff --git a/src/mqt/qudits/quantum_circuit/gates/s.py b/src/mqt/qudits/quantum_circuit/gates/s.py index 44ae0ace..15ba6ad3 100644 --- a/src/mqt/qudits/quantum_circuit/gates/s.py +++ b/src/mqt/qudits/quantum_circuit/gates/s.py @@ -40,15 +40,20 @@ def __init__( def __array__(self) -> NDArray: # noqa: PLW3201 if self.dimensions == 2: - return np.array([[1, 0], [0, 1j]]) - matrix = np.zeros((self.dimensions, self.dimensions), dtype="complex") - for i in range(self.dimensions): - omega = np.e ** (2 * np.pi * 1j / self.dimensions) - omega **= np.mod(i * (i + 1) / 2, self.dimensions) - array = np.zeros(self.dimensions, dtype="complex") - array[i] = 1 - result = omega * np.outer(array, array) - matrix += result + matrix = np.array([[1, 0], [0, 1j]]) + else: + matrix = np.zeros((self.dimensions, self.dimensions), dtype="complex") + for i in range(self.dimensions): + omega = np.e ** (2 * np.pi * 1j / self.dimensions) + omega **= np.mod(i * (i + 1) / 2, self.dimensions) + array = np.zeros(self.dimensions, dtype="complex") + array[i] = 1 + result = omega * np.outer(array, array) + matrix += result + + if self.dagger: + return matrix.conj().T + return matrix @staticmethod @@ -75,3 +80,9 @@ def is_prime(n: int) -> bool: def dimensions(self) -> int: assert isinstance(self._dimensions, int), "Dimensions must be an integer" return self._dimensions + + def to_qasm(self) -> str: + string_description = self.__qasm__() + if self.dagger: + return "inv @ " + string_description + return string_description diff --git a/src/mqt/qudits/quantum_circuit/gates/virt_rz.py b/src/mqt/qudits/quantum_circuit/gates/virt_rz.py index a2d87baf..9722148d 100644 --- a/src/mqt/qudits/quantum_circuit/gates/virt_rz.py +++ b/src/mqt/qudits/quantum_circuit/gates/virt_rz.py @@ -49,6 +49,10 @@ def __array__(self) -> NDArray: # noqa: PLW3201 return matrix + def _dagger_properties(self) -> None: + self.phi *= -1 + self.update_params([self.lev_a, self.phi]) + def validate_parameter(self, param: Parameter) -> bool: if param is None: return False diff --git a/src/mqt/qudits/quantum_circuit/gates/x.py b/src/mqt/qudits/quantum_circuit/gates/x.py index 1ee4b9d7..9baea353 100644 --- a/src/mqt/qudits/quantum_circuit/gates/x.py +++ b/src/mqt/qudits/quantum_circuit/gates/x.py @@ -42,9 +42,19 @@ def __array__(self) -> NDArray: # noqa: PLW3201 array1[i_plus_1] = 1 array2[i] = 1 matrix += np.outer(array1, array2) + + if self.dagger: + return matrix.conj().T + return matrix @property def dimensions(self) -> int: assert isinstance(self._dimensions, int), "Dimensions must be an integer" return self._dimensions + + def to_qasm(self) -> str: + string_description = self.__qasm__() + if self.dagger: + return "inv @ " + string_description + return string_description diff --git a/src/mqt/qudits/quantum_circuit/gates/z.py b/src/mqt/qudits/quantum_circuit/gates/z.py index f8c1b3ef..8cf85221 100644 --- a/src/mqt/qudits/quantum_circuit/gates/z.py +++ b/src/mqt/qudits/quantum_circuit/gates/z.py @@ -44,9 +44,18 @@ def __array__(self) -> NDArray: # noqa: PLW3201 result = omega * np.outer(array, array) matrix += result + if self.dagger: + return matrix.conj().T + return matrix @property def dimensions(self) -> int: assert isinstance(self._dimensions, int), "Dimensions must be an integer in Z gate" return self._dimensions + + def to_qasm(self) -> str: + string_description = self.__qasm__() + if self.dagger: + return "inv @ " + string_description + return string_description diff --git a/src/mqt/qudits/quantum_circuit/qasm.py b/src/mqt/qudits/quantum_circuit/qasm.py index d73409a6..c7e8b69c 100644 --- a/src/mqt/qudits/quantum_circuit/qasm.py +++ b/src/mqt/qudits/quantum_circuit/qasm.py @@ -95,13 +95,15 @@ def parse_gate( ) -> bool: match = rgxs["gate_matrix"].search(line) if match: - label = match.group(1) - params = match.group(2) - qudits = match.group(3) - ctl_pattern = match.group(5) - ctl_qudits = match.group(6) - ctl_levels = match.group(8) - + inv = match.group(1) + label = match.group(2) + params = match.group(3) + qudits = match.group(4) + ctl_pattern = match.group(6) + ctl_qudits = match.group(7) + ctl_levels = match.group(9) + + gate_is_dagger = inv is not None # Evaluate params using NumPy and NumExpr if params: if ".npy" in params: @@ -145,7 +147,13 @@ def parse_gate( controls = None else: controls = ControlData(qudits_control_list, qudits_levels_list) - gate_dict = {"name": label, "params": params, "qudits": qudits_list, "controls": controls} + gate_dict = { + "name": label, + "params": params, + "qudits": qudits_list, + "controls": controls, + "dagger": gate_is_dagger, + } gates.append(gate_dict) @@ -182,9 +190,8 @@ def parse_ditqasm2_str(self, contents: str) -> dict[str, Any]: # "gate_matrix": # re.compile(r"(\w+)\s*(?:\(([^)]*)\))?\s*(\w+\[\other_size+\]\s*(,\s*\w+\[\other_size+\])*)\s*;"), "gate_matrix": re.compile( - r"(\w+)\s*(?:\(([^)]*)\))?\s*(\w+\[\d+\]\s*(,\s*\w+\[\d+\])*)\s*" - r"(ctl(\s+\w+\[\d+\]\s*(\s*\w+\s*\[\d+\])*)\s*(\[(\d+(,\s*\d+)*)\]))?" - r"\s*;" + r"^(inv\s*@\s*)?(\w+)\s*(?:\(([^)]*)\))?\s*(\w+\[\d+\]\s*(,\s*\w+\[\d+\])*)" + r"\s*(ctl(\s+\w+\[\d+\]\s*(\s*\w+\s*\[\d+\])*)\s*(\[(\d+(,\s*\d+)*)\]))?\s*;" ), "error": re.compile(r"^(gate_matrix|if)"), "ignore": re.compile(r"^(measure|barrier)"), diff --git a/src/mqt/qudits/simulation/backends/fake_backends/fake_traps2six.py b/src/mqt/qudits/simulation/backends/fake_backends/fake_traps2six.py index 1fea81a0..01cfef61 100644 --- a/src/mqt/qudits/simulation/backends/fake_backends/fake_traps2six.py +++ b/src/mqt/qudits/simulation/backends/fake_backends/fake_traps2six.py @@ -4,6 +4,7 @@ from typing_extensions import Unpack +from mqt.qudits.simulation.noise_tools import SubspaceNoise from mqt.qudits.simulation.noise_tools.noise import Noise from ....core import LevelGraph @@ -34,6 +35,7 @@ def energy_level_graphs(self) -> list[LevelGraph]: e_graphs: list[LevelGraph] = [] # declare the edges on the energy level graph between logic states . edges = [ + (1, 0, {"delta_m": 0, "sensitivity": 3}), (2, 0, {"delta_m": 0, "sensitivity": 3}), (3, 0, {"delta_m": 0, "sensitivity": 3}), (4, 0, {"delta_m": 0, "sensitivity": 4}), @@ -53,6 +55,7 @@ def energy_level_graphs(self) -> list[LevelGraph]: graph_0 = LevelGraph(edges, nodes, nmap, [1]) # declare the edges on the energy level graph between logic states . edges_1 = [ + (1, 0, {"delta_m": 0, "sensitivity": 3}), (2, 0, {"delta_m": 0, "sensitivity": 3}), (3, 0, {"delta_m": 0, "sensitivity": 3}), (4, 0, {"delta_m": 0, "sensitivity": 4}), @@ -76,27 +79,32 @@ def energy_level_graphs(self) -> list[LevelGraph]: return self._energy_level_graphs def __noise_model(self) -> NoiseModel | None: - # Depolarizing quantum errors - local_error = Noise(probability_depolarizing=0.001, probability_dephasing=0.001) - local_error_rz = Noise(probability_depolarizing=0.03, probability_dephasing=0.03) - entangling_error = Noise(probability_depolarizing=0.1, probability_dephasing=0.001) - entangling_error_extra = Noise(probability_depolarizing=0.1, probability_dephasing=0.1) - entangling_error_on_target = Noise(probability_depolarizing=0.1, probability_dephasing=0.0) - entangling_error_on_control = Noise(probability_depolarizing=0.01, probability_dephasing=0.0) + # Depolarizing and Dephasing quantum errors + basic_error = Noise(probability_depolarizing=0.005, probability_dephasing=0.005) + basic_subspace_dynamic_error = SubspaceNoise( + probability_depolarizing=2e-4, probability_dephasing=2e-4, levels=[] + ) - # Add errors to noise_tools model + basic_subspace_dynamic_error_rz = SubspaceNoise( + probability_depolarizing=6e-4, probability_dephasing=4e-4, levels=[] + ) + subspace_error_01 = SubspaceNoise(probability_depolarizing=0.005, probability_dephasing=0.005, levels=(0, 1)) + subspace_error_01_cex = SubspaceNoise( + probability_depolarizing=0.010, probability_dephasing=0.010, levels=(0, 1) + ) + # Add errors to noise_tools model noise_model = NoiseModel() # We know that the architecture is only two qudits # Very noisy gate_matrix - noise_model.add_all_qudit_quantum_error(local_error, ["csum"]) - # Entangling gates - noise_model.add_nonlocal_quantum_error(entangling_error, ["cx", "ls", "ms"]) - noise_model.add_nonlocal_quantum_error_on_target(entangling_error_on_target, ["cx", "ls", "ms"]) - noise_model.add_nonlocal_quantum_error_on_control(entangling_error_on_control, ["csum", "cx", "ls", "ms"]) - # Super noisy Entangling gates - noise_model.add_nonlocal_quantum_error(entangling_error_extra, ["csum"]) - # Local Gates - noise_model.add_quantum_error_locally(local_error, ["h", "rxy", "s", "x", "z"]) - noise_model.add_quantum_error_locally(local_error_rz, ["rz", "virtrz"]) + noise_model.add_nonlocal_quantum_error(basic_error, ["csum", "ls"]) + noise_model.add_quantum_error_locally( + basic_error, ["cuone", "cutwo", "cumulti", "h", "perm", "rdu", "s", "x", "z"] + ) + + # Physical gates + noise_model.add_nonlocal_quantum_error(subspace_error_01, ["ms"]) + noise_model.add_nonlocal_quantum_error(subspace_error_01_cex, ["cx"]) + noise_model.add_quantum_error_locally(basic_subspace_dynamic_error, ["rxy", "rh"]) + noise_model.add_quantum_error_locally(basic_subspace_dynamic_error_rz, ["rz"]) self.noise_model = noise_model return noise_model diff --git a/src/mqt/qudits/simulation/backends/fake_backends/fake_traps2three.py b/src/mqt/qudits/simulation/backends/fake_backends/fake_traps2three.py index 7bc8be8b..735c420b 100644 --- a/src/mqt/qudits/simulation/backends/fake_backends/fake_traps2three.py +++ b/src/mqt/qudits/simulation/backends/fake_backends/fake_traps2three.py @@ -4,7 +4,7 @@ from typing_extensions import Unpack -from mqt.qudits.simulation.noise_tools.noise import Noise +from mqt.qudits.simulation.noise_tools.noise import Noise, SubspaceNoise from ....core import LevelGraph from ...noise_tools import NoiseModel @@ -49,7 +49,7 @@ def energy_level_graphs(self) -> list[LevelGraph]: nodes = [0, 1, 2] # declare physical levels in order of mapping of the logic states just declared . # i.e. here we will have Logic 0 -> Phys. 0, have Logic 1 -> Phys. 1, have Logic 2 -> Phys. 2 . - nmap = [0, 2, 1] + nmap = [1, 2, 0] # Construct the qudit energy level graph, the last field is the list of logic state that are used for the # calibrations of the operations. note: only the first is one counts in our current cost function. graph_0 = LevelGraph(edges, nodes, nmap, [1]) @@ -62,7 +62,7 @@ def energy_level_graphs(self) -> list[LevelGraph]: nodes = [0, 1, 2] # declare physical levels in order of mapping of the logic states just declared . # i.e. here we will have Logic 0 -> Phys. 0, have Logic 1 -> Phys. 1, have Logic 2 -> Phys. 2 . - nmap = [0, 2, 1] + nmap = [0, 1, 2] # Construct the qudit energy level graph, the last field is the list of logic state that are used for the # calibrations of the operations. note: only the first is one counts in our current cost function. graph_1 = LevelGraph(edges, nodes, nmap, [1]) @@ -72,29 +72,32 @@ def energy_level_graphs(self) -> list[LevelGraph]: return self._energy_level_graphs def __noise_model(self) -> NoiseModel: - """Noise model coded in plain sight, just for prototyping reasons.""" - # Depolarizing quantum errors - local_error = Noise(probability_depolarizing=0.001, probability_dephasing=0.001) - local_error_rz = Noise(probability_depolarizing=0.03, probability_dephasing=0.03) - entangling_error = Noise(probability_depolarizing=0.1, probability_dephasing=0.001) - entangling_error_extra = Noise(probability_depolarizing=0.1, probability_dephasing=0.1) - entangling_error_on_target = Noise(probability_depolarizing=0.1, probability_dephasing=0.0) - entangling_error_on_control = Noise(probability_depolarizing=0.01, probability_dephasing=0.0) + # Depolarizing and Dephasing quantum errors + basic_error = Noise(probability_depolarizing=0.005, probability_dephasing=0.005) + basic_subspace_dynamic_error = SubspaceNoise( + probability_depolarizing=2e-4, probability_dephasing=2e-4, levels=[] + ) - # Add errors to noise_tools model + basic_subspace_dynamic_error_rz = SubspaceNoise( + probability_depolarizing=6e-4, probability_dephasing=4e-4, levels=[] + ) + subspace_error_01 = SubspaceNoise(probability_depolarizing=0.005, probability_dephasing=0.005, levels=(0, 1)) + subspace_error_01_cex = SubspaceNoise( + probability_depolarizing=0.010, probability_dephasing=0.010, levels=(0, 1) + ) + # Add errors to noise_tools model noise_model = NoiseModel() # We know that the architecture is only two qudits # Very noisy gate_matrix - noise_model.add_all_qudit_quantum_error(local_error, ["csum"]) - # Entangling gates - noise_model.add_nonlocal_quantum_error(entangling_error, ["cx", "ls", "ms"]) - noise_model.add_nonlocal_quantum_error_on_target(entangling_error_on_target, ["cx", "ls", "ms"]) - noise_model.add_nonlocal_quantum_error_on_control(entangling_error_on_control, ["csum", "cx", "ls", "ms"]) - # Super noisy Entangling gates - noise_model.add_nonlocal_quantum_error(entangling_error_extra, ["csum"]) - # Local Gates - noise_model.add_quantum_error_locally(local_error, ["h", "rxy", "s", "x", "z"]) - noise_model.add_quantum_error_locally(local_error_rz, ["rz", "virtrz"]) + noise_model.add_nonlocal_quantum_error(basic_error, ["csum", "ls"]) + noise_model.add_quantum_error_locally( + basic_error, ["cuone", "cutwo", "cumulti", "h", "perm", "rdu", "s", "x", "z"] + ) + # Physical gates + noise_model.add_nonlocal_quantum_error(subspace_error_01, ["ms"]) + noise_model.add_nonlocal_quantum_error(subspace_error_01_cex, ["cx"]) + noise_model.add_quantum_error_locally(basic_subspace_dynamic_error, ["rxy", "rh"]) + noise_model.add_quantum_error_locally(basic_subspace_dynamic_error_rz, ["rz"]) self.noise_model = noise_model return noise_model diff --git a/src/mqt/qudits/simulation/backends/fake_backends/fake_traps3six.py b/src/mqt/qudits/simulation/backends/fake_backends/fake_traps3six.py index b926bdbd..a7429bee 100644 --- a/src/mqt/qudits/simulation/backends/fake_backends/fake_traps3six.py +++ b/src/mqt/qudits/simulation/backends/fake_backends/fake_traps3six.py @@ -4,7 +4,7 @@ from typing_extensions import Unpack -from mqt.qudits.simulation.noise_tools.noise import Noise +from mqt.qudits.simulation.noise_tools.noise import Noise, SubspaceNoise from ....core import LevelGraph from ...noise_tools import NoiseModel @@ -54,7 +54,7 @@ def energy_level_graphs(self) -> list[LevelGraph]: nodes = [0, 1, 2, 3, 4, 5] # declare physical levels in order of mapping of the logic states just declared . # i.e. here we will have Logic 0 -> Phys. 0, have Logic 1 -> Phys. 1, have Logic 2 -> Phys. 2 . - nmap = [0, 1, 2, 3, 4, 5] + nmap = [1, 0, 2, 3, 4, 5] # Construct the qudit energy level graph, the last field is the list of logic state that are used for the # calibrations of the operations. note: only the first is one counts in our current cost function. graph_0 = LevelGraph(edges, nodes, nmap, [1]) @@ -73,7 +73,7 @@ def energy_level_graphs(self) -> list[LevelGraph]: nodes_1 = [0, 1, 2, 3, 4, 5] # declare physical levels in order of mapping of the logic states just declared . # i.e. here we will have Logic 0 -> Phys. 0, have Logic 1 -> Phys. 1, have Logic 2 -> Phys. 2 . - nmap_1 = [0, 1, 2, 3, 4, 5] + nmap_1 = [2, 1, 0, 3, 4, 5] # Construct the qudit energy level graph, the last field is the list of logic state that are used for the # calibrations of the operations. note: only the first is one counts in our current cost function. graph_1 = LevelGraph(edges_1, nodes_1, nmap_1, [1]) @@ -93,7 +93,7 @@ def energy_level_graphs(self) -> list[LevelGraph]: nodes_2 = [0, 1, 2, 3, 4, 5] # declare physical levels in order of mapping of the logic states just declared . # i.e. here we will have Logic 0 -> Phys. 0, have Logic 1 -> Phys. 1, have Logic 2 -> Phys. 2 . - nmap_2 = [0, 1, 2, 3, 4, 5] + nmap_2 = [0, 2, 1, 3, 4, 5] # Construct the qudit energy level graph, the last field is the list of logic state that are used for the # calibrations of the operations. note: only the first is one counts in our current cost function. graph_2 = LevelGraph(edges_2, nodes_2, nmap_2, [1]) @@ -104,28 +104,32 @@ def energy_level_graphs(self) -> list[LevelGraph]: return self._energy_level_graphs def __noise_model(self) -> NoiseModel: - # Depolarizing quantum errors - local_error = Noise(probability_depolarizing=0.001, probability_dephasing=0.001) - local_error_rz = Noise(probability_depolarizing=0.03, probability_dephasing=0.03) - entangling_error = Noise(probability_depolarizing=0.1, probability_dephasing=0.001) - entangling_error_extra = Noise(probability_depolarizing=0.1, probability_dephasing=0.1) - entangling_error_on_target = Noise(probability_depolarizing=0.1, probability_dephasing=0.0) - entangling_error_on_control = Noise(probability_depolarizing=0.01, probability_dephasing=0.0) + # Depolarizing and Dephasing quantum errors + basic_error = Noise(probability_depolarizing=0.005, probability_dephasing=0.005) + basic_subspace_dynamic_error = SubspaceNoise( + probability_depolarizing=2e-4, probability_dephasing=2e-4, levels=[] + ) - # Add errors to noise_tools model + basic_subspace_dynamic_error_rz = SubspaceNoise( + probability_depolarizing=6e-4, probability_dephasing=4e-4, levels=[] + ) + subspace_error_01 = SubspaceNoise(probability_depolarizing=0.005, probability_dephasing=0.005, levels=(0, 1)) + subspace_error_01_cex = SubspaceNoise( + probability_depolarizing=0.010, probability_dephasing=0.010, levels=(0, 1) + ) + # Add errors to noise_tools model noise_model = NoiseModel() # We know that the architecture is only two qudits # Very noisy gate_matrix - noise_model.add_all_qudit_quantum_error(local_error, ["csum"]) - # Entangling gates - noise_model.add_nonlocal_quantum_error(entangling_error, ["cx", "ls", "ms"]) - noise_model.add_nonlocal_quantum_error_on_target(entangling_error_on_target, ["cx", "ls", "ms"]) - noise_model.add_nonlocal_quantum_error_on_control(entangling_error_on_control, ["csum", "cx", "ls", "ms"]) - # Super noisy Entangling gates - noise_model.add_nonlocal_quantum_error(entangling_error_extra, ["csum"]) - # Local Gates - noise_model.add_quantum_error_locally(local_error, ["h", "rxy", "s", "x", "z"]) - noise_model.add_quantum_error_locally(local_error_rz, ["rz", "virtrz"]) + noise_model.add_nonlocal_quantum_error(basic_error, ["csum", "ls"]) + noise_model.add_quantum_error_locally( + basic_error, ["cuone", "cutwo", "cumulti", "h", "perm", "rdu", "s", "x", "z"] + ) + # Physical gates + noise_model.add_nonlocal_quantum_error(subspace_error_01, ["ms"]) + noise_model.add_nonlocal_quantum_error(subspace_error_01_cex, ["cx"]) + noise_model.add_quantum_error_locally(basic_subspace_dynamic_error, ["rxy", "rh"]) + noise_model.add_quantum_error_locally(basic_subspace_dynamic_error_rz, ["rz"]) self.noise_model = noise_model return noise_model diff --git a/src/mqt/qudits/simulation/backends/fake_backends/fake_traps8seven.py b/src/mqt/qudits/simulation/backends/fake_backends/fake_traps8seven.py new file mode 100644 index 00000000..e9d555b7 --- /dev/null +++ b/src/mqt/qudits/simulation/backends/fake_backends/fake_traps8seven.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from typing_extensions import Unpack + +from ....core import LevelGraph +from ...noise_tools import Noise, NoiseModel, SubspaceNoise +from ..tnsim import TNSim + +if TYPE_CHECKING: + from ... import MQTQuditProvider + from ..backendv2 import Backend + + +class FakeIonTraps8Seven(TNSim): + @property + def version(self) -> int: + return 0 + + def __init__( + self, + provider: MQTQuditProvider, + **fields: Unpack[Backend.DefaultOptions], + ) -> None: + super().__init__( + provider=provider, + name="FakeTrap8Seven", + description="A Fake backend of an ion trap qudit machine with 8 ions and 7 levels each", + **fields, + ) + self.options["noise_model"] = self.__noise_model() + self.author = "" + self._energy_level_graphs: list[LevelGraph] = [] + + @property + def energy_level_graphs(self) -> list[LevelGraph]: + if len(self._energy_level_graphs) == 0: + e_graphs: list[LevelGraph] = [] + + # Create graphs for all 8 ions + for i in range(8): + # declare the edges on the energy level graph between logic states + # we have to fake a direct connection between 0 and 1 to make transpilations + # physically compatible also for small systems + edges = [ + (0, 1, {"delta_m": 0, "sensitivity": 4}), + (2, 0, {"delta_m": 0, "sensitivity": 3}), + # (3, 0, {"delta_m": 0, "sensitivity": 3}), + (4, 0, {"delta_m": 0, "sensitivity": 3}), + (5, 0, {"delta_m": 0, "sensitivity": 4}), + (6, 0, {"delta_m": 0, "sensitivity": 4}), + (1, 2, {"delta_m": 0, "sensitivity": 4}), + (1, 3, {"delta_m": 0, "sensitivity": 3}), + (1, 4, {"delta_m": 0, "sensitivity": 3}), + (1, 5, {"delta_m": 0, "sensitivity": 4}), + (1, 6, {"delta_m": 0, "sensitivity": 4}), + ] + + # name explicitly the logic states (0 through 6 for seven levels) + nodes = list(range(7)) + + # Different mappings for different ions + if i % 3 == 0: + nmap = [1, 0, 2, 3, 4, 5, 6] # Similar to graph_0 in original + elif i % 3 == 1: + nmap = [2, 1, 0, 3, 4, 5, 6] # Similar to graph_1 in original + else: + nmap = [0, 2, 1, 3, 4, 5, 6] # Similar to graph_2 in original + + # Construct the qudit energy level graph + graph = LevelGraph(edges, nodes, nmap, [1]) + e_graphs.append(graph) + + self._energy_level_graphs = e_graphs + return self._energy_level_graphs + + def __noise_model(self) -> NoiseModel: + # Depolarizing and Dephasing quantum errors + basic_error = Noise(probability_depolarizing=0.005, probability_dephasing=0.005) + basic_subspace_dynamic_error = SubspaceNoise( + probability_depolarizing=2e-4, probability_dephasing=2e-4, levels=[] + ) + + basic_subspace_dynamic_error_rz = SubspaceNoise( + probability_depolarizing=6e-4, probability_dephasing=4e-4, levels=[] + ) + subspace_error_01 = SubspaceNoise(probability_depolarizing=0.005, probability_dephasing=0.005, levels=(0, 1)) + subspace_error_01_cex = SubspaceNoise( + probability_depolarizing=0.010, probability_dephasing=0.010, levels=(0, 1) + ) + + # Add errors to noise_tools model + noise_model = NoiseModel() # We know that the architecture is only two qudits + # Very noisy gate_matrix + noise_model.add_nonlocal_quantum_error(basic_error, ["csum", "ls"]) + noise_model.add_quantum_error_locally( + basic_error, ["cuone", "cutwo", "cumulti", "h", "perm", "rdu", "s", "x", "z"] + ) + + # Physical gates + noise_model.add_nonlocal_quantum_error(subspace_error_01, ["ms"]) + noise_model.add_nonlocal_quantum_error(subspace_error_01_cex, ["cx"]) + noise_model.add_quantum_error_locally(basic_subspace_dynamic_error, ["rxy", "rh"]) + noise_model.add_quantum_error_locally(basic_subspace_dynamic_error_rz, ["rz"]) + self.noise_model = noise_model + return noise_model diff --git a/src/mqt/qudits/simulation/backends/innsbruck_01.py b/src/mqt/qudits/simulation/backends/innsbruck_01.py index e2913064..0e873216 100644 --- a/src/mqt/qudits/simulation/backends/innsbruck_01.py +++ b/src/mqt/qudits/simulation/backends/innsbruck_01.py @@ -2,18 +2,18 @@ from typing import TYPE_CHECKING -import numpy as np from typing_extensions import Unpack # from pyseq.mqt_qudits_runner.sequence_runner import quantum_circuit_runner from ...core import LevelGraph -from ..jobs import Job, JobResult +from ..jobs import Job +from ..jobs.client_api import APIClient +from ..noise_tools import Noise, NoiseModel, SubspaceNoise from .backendv2 import Backend if TYPE_CHECKING: from ...quantum_circuit import QuantumCircuit from .. import MQTQuditProvider - from ..noise_tools import NoiseModel class Innsbruck01(Backend): @@ -36,36 +36,46 @@ def __init__( self.options["noise_model"] = self.__noise_model() self.author = "" self._energy_level_graphs: list[LevelGraph] = [] + self._api_client = APIClient() @property def energy_level_graphs(self) -> list[LevelGraph]: if len(self._energy_level_graphs) == 0: e_graphs: list[LevelGraph] = [] - # declare the edges on the energy level graph between logic states . - edges = [ - (0, 1, {"delta_m": 0, "sensitivity": 3, "carrier": 0}), - (1, 2, {"delta_m": 0, "sensitivity": 4, "carrier": 1}), - ] - # name explicitly the logic states . - nodes = [0, 1, 2] - # declare physical levels in order of mapping of the logic states just declared . - # i.e. here we will have Logic 0 -> Phys. 0, have Logic 1 -> Phys. 1, have Logic 2 -> Phys. 2 . - nmap = [0, 1, 2] - graph_0 = LevelGraph(edges, nodes, nmap, [1]) - - edges_1 = [ - (0, 1, {"delta_m": 0, "sensitivity": 3, "carrier": 0}), - (1, 2, {"delta_m": 0, "sensitivity": 4, "carrier": 1}), - ] - # name explicitly the logic states . - nodes_1 = [0, 1, 2] - # declare physical levels in order of mapping of the logic states just declared . - # i.e. here we will have Logic 0 -> Phys. 0, have Logic 1 -> Phys. 1, have Logic 2 -> Phys. 2 . - nmap_1 = [0, 1, 2] - - graph_1 = LevelGraph(edges_1, nodes_1, nmap_1, [1]) - - e_graphs.extend((graph_0, graph_1)) + + # Create graphs for all 8 ions + for i in range(8): + # declare the edges on the energy level graph between logic states + # we have to fake a direct connection between 0 and 1 to make transpilations + # physically compatible also for small systems + edges = [ + (0, 1, {"delta_m": 0, "sensitivity": 4}), + (2, 0, {"delta_m": 0, "sensitivity": 3}), + # (3, 0, {"delta_m": 0, "sensitivity": 3}), + (4, 0, {"delta_m": 0, "sensitivity": 3}), + (5, 0, {"delta_m": 0, "sensitivity": 4}), + (6, 0, {"delta_m": 0, "sensitivity": 4}), + (1, 2, {"delta_m": 0, "sensitivity": 4}), + (1, 3, {"delta_m": 0, "sensitivity": 3}), + (1, 4, {"delta_m": 0, "sensitivity": 3}), + (1, 5, {"delta_m": 0, "sensitivity": 4}), + (1, 6, {"delta_m": 0, "sensitivity": 4}), + ] + + # name explicitly the logic states (0 through 6 for seven levels) + nodes = list(range(7)) + + # Different mappings for different ions + if i % 3 == 0: + nmap = [1, 0, 2, 3, 4, 5, 6] # Similar to graph_0 in original + elif i % 3 == 1: + nmap = [2, 1, 0, 3, 4, 5, 6] # Similar to graph_1 in original + else: + nmap = [0, 2, 1, 3, 4, 5, 6] # Similar to graph_2 in original + + # Construct the qudit energy level graph + graph = LevelGraph(edges, nodes, nmap, [1]) + e_graphs.append(graph) self._energy_level_graphs = e_graphs return self._energy_level_graphs @@ -75,11 +85,39 @@ def edge_to_carrier(self, leva: int, levb: int, graph_index: int) -> int: edge_data: dict[str, int] = e_graph.get_edge_data(leva, levb) return edge_data["carrier"] - def __noise_model(self) -> NoiseModel | None: - return self.noise_model + def __noise_model(self) -> NoiseModel: + # Depolarizing and Dephasing quantum errors + basic_error = Noise(probability_depolarizing=0.005, probability_dephasing=0.005) + basic_subspace_dynamic_error = SubspaceNoise( + probability_depolarizing=2e-4, probability_dephasing=2e-4, levels=[] + ) + + basic_subspace_dynamic_error_rz = SubspaceNoise( + probability_depolarizing=6e-4, probability_dephasing=4e-4, levels=[] + ) + subspace_error_01 = SubspaceNoise(probability_depolarizing=0.005, probability_dephasing=0.005, levels=(0, 1)) + subspace_error_01_cex = SubspaceNoise( + probability_depolarizing=0.010, probability_dephasing=0.010, levels=(0, 1) + ) + + # Add errors to noise_tools model + noise_model = NoiseModel() + # Very noisy gate_matrix + noise_model.add_nonlocal_quantum_error(basic_error, ["csum", "ls"]) + noise_model.add_quantum_error_locally( + basic_error, ["cuone", "cutwo", "cumulti", "h", "perm", "rdu", "s", "x", "z"] + ) + + # Physical gates + noise_model.add_nonlocal_quantum_error(subspace_error_01, ["ms"]) + noise_model.add_nonlocal_quantum_error(subspace_error_01_cex, ["cx"]) + noise_model.add_quantum_error_locally(basic_subspace_dynamic_error, ["rxy", "rh"]) + noise_model.add_quantum_error_locally(basic_subspace_dynamic_error_rz, ["rz"]) + self.noise_model = noise_model + return noise_model def run(self, circuit: QuantumCircuit, **options: Unpack[Backend.DefaultOptions]) -> Job: - job = Job(self) + Job(self) self._options.update(options) self.noise_model = self._options.get("noise_model", None) @@ -89,17 +127,11 @@ def run(self, circuit: QuantumCircuit, **options: Unpack[Backend.DefaultOptions] self.file_path = self._options.get("file_path", None) self.file_name = self._options.get("file_name", None) - assert self.shots >= 50, "Number of shots should be above 50" - self.execute(circuit) - job.set_result(JobResult(state_vector=np.array([]), counts=self.outcome)) + job_id = self._api_client.submit_job(circuit, self.shots, self.energy_level_graphs) + return Job(self, job_id, self._api_client) - return job + def close(self) -> None: + self._api_client.close() def execute(self, circuit: QuantumCircuit, noise_model: NoiseModel | None = None) -> None: - _ = noise_model # Silences the unused argument warning - self.system_sizes = circuit.dimensions - self.circ_operations = circuit.instructions - - # quantum_circuit_runner(self.circ_operations) - - self.outcome = [] # quantum_circuit_runner(metadata, self.system_sizes) + pass diff --git a/src/mqt/qudits/simulation/backends/misim.py b/src/mqt/qudits/simulation/backends/misim.py index 8ed35dbf..560efbad 100644 --- a/src/mqt/qudits/simulation/backends/misim.py +++ b/src/mqt/qudits/simulation/backends/misim.py @@ -46,9 +46,11 @@ def run(self, circuit: QuantumCircuit, **options: Unpack[Backend.DefaultOptions] if self.noise_model is not None: assert self.shots >= 50, "Number of shots should be above 50" - job.set_result(JobResult(state_vector=self.execute(circuit), counts=stochastic_simulation(self, circuit))) + job.set_result( + JobResult(job.job_id, state_vector=self.execute(circuit), counts=stochastic_simulation(self, circuit)) + ) else: - job.set_result(JobResult(state_vector=self.execute(circuit), counts=[])) + job.set_result(JobResult(job.job_id, state_vector=self.execute(circuit), counts=[])) return job diff --git a/src/mqt/qudits/simulation/backends/tnsim.py b/src/mqt/qudits/simulation/backends/tnsim.py index 3ac8a8d9..e73b056f 100644 --- a/src/mqt/qudits/simulation/backends/tnsim.py +++ b/src/mqt/qudits/simulation/backends/tnsim.py @@ -49,9 +49,11 @@ def run(self, circuit: QuantumCircuit, **options: Unpack[Backend.DefaultOptions] if self.noise_model is not None: assert self.shots >= 50, "Number of shots should be above 50" - job.set_result(JobResult(state_vector=self.execute(circuit), counts=stochastic_simulation(self, circuit))) + job.set_result( + JobResult(job.job_id, state_vector=self.execute(circuit), counts=stochastic_simulation(self, circuit)) + ) else: - job.set_result(JobResult(state_vector=self.execute(circuit), counts=[])) + job.set_result(JobResult(job.job_id, state_vector=self.execute(circuit), counts=[])) return job diff --git a/src/mqt/qudits/simulation/jobs/client_api.py b/src/mqt/qudits/simulation/jobs/client_api.py new file mode 100644 index 00000000..b8a1c00b --- /dev/null +++ b/src/mqt/qudits/simulation/jobs/client_api.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +from time import sleep +from typing import TYPE_CHECKING, cast + +import requests + +from mqt.qudits.simulation.jobs import JobResult +from mqt.qudits.simulation.jobs.config_api import ( + BASE_URL, + JOB_RESULT_ENDPOINT, + JOB_STATUS_ENDPOINT, + SUBMIT_JOB_ENDPOINT, +) +from mqt.qudits.simulation.jobs.jobstatus import JobStatus + +if TYPE_CHECKING: + from collections.abc import Callable + + from mqt.qudits.core import LevelGraph + from mqt.qudits.quantum_circuit import QuantumCircuit + + +class APIClient: + def __init__(self) -> None: + self.session = requests.Session() + + def close(self) -> None: + self.session.close() + + def submit_job(self, circuit: QuantumCircuit, shots: int, energy_level_graphs: list[LevelGraph]) -> str: + url = f"{BASE_URL}{SUBMIT_JOB_ENDPOINT}" + payload = { + "circuit": circuit.to_qasm(), + "shots": shots, + "energy_level_graphs": list(energy_level_graphs), + } + response = self.session.post(url, json=payload) + if response.status_code == 200: + data = response.json() + return cast("str", data.get("job_id")) + msg = f"Job submission failed with status code {response.status_code}" + raise RuntimeError(msg) + + def get_job_status(self, job_id: str) -> JobStatus: + url = f"{BASE_URL}{JOB_STATUS_ENDPOINT}/{job_id}" + response = self.session.get(url) + if response.status_code == 200: + data = response.json() + return JobStatus.from_string(data.get("status")) + msg = f"Failed to get job status with status code {response.status_code}" + raise RuntimeError(msg) + + def get_job_result(self, job_id: str) -> JobResult: + url = f"{BASE_URL}{JOB_RESULT_ENDPOINT}/{job_id}" + response = self.session.get(url) + if response.status_code == 200: + data = response.json() + return JobResult(data) + msg = f"Failed to get job result with status code {response.status_code}" + raise RuntimeError(msg) + + def wait_for_job_completion( + self, + job_id: str, + callback: Callable[[str, JobStatus], None] | None = None, + polling_interval: float = 5, + ) -> JobStatus: + while True: + status = self.get_job_status(job_id) + if callback: + callback(job_id, status) + if status.is_final: + return status + sleep(polling_interval) diff --git a/src/mqt/qudits/simulation/jobs/config_api.py b/src/mqt/qudits/simulation/jobs/config_api.py new file mode 100644 index 00000000..48c28e42 --- /dev/null +++ b/src/mqt/qudits/simulation/jobs/config_api.py @@ -0,0 +1,6 @@ +from __future__ import annotations + +BASE_URL = "https://quantum-computer-api.example.com" +SUBMIT_JOB_ENDPOINT = "/submit_job" +JOB_STATUS_ENDPOINT = "/job_status" +JOB_RESULT_ENDPOINT = "/job_result" diff --git a/src/mqt/qudits/simulation/jobs/job.py b/src/mqt/qudits/simulation/jobs/job.py index f5339ad1..85c5cbe2 100644 --- a/src/mqt/qudits/simulation/jobs/job.py +++ b/src/mqt/qudits/simulation/jobs/job.py @@ -1,10 +1,7 @@ from __future__ import annotations -import os -import time -from typing import TYPE_CHECKING, Any, NoReturn +from typing import TYPE_CHECKING -from ...exceptions import JobError, JobTimeoutError from .jobstatus import JobStatus if TYPE_CHECKING: @@ -12,106 +9,76 @@ from ..backends.backendv2 import Backend from . import JobResult + from .client_api import APIClient class Job: - """Class to handle jobs. - - This first version of the Backend abstract class is written to be mostly - backwards compatible with the legacy providers interface. This was done to ease - the transition for users and provider maintainers to the new versioned providers. - Expect future versions of this abstract class to change the data model and - interface. - """ - - version = 1 - _async = True - - def __init__(self, backend: Backend | None, job_id: str = "auto", **kwargs: dict[str, Any]) -> None: - """Initializes the asynchronous job. - - Args: - backend: the backend used to run the job. - job_id: a unique id in the context of the backend used to run the job. - kwargs: Any key-value metadata to associate with this job. - """ - if job_id == "auto": - current_time = int(time.time() * 1000) - self._job_id = str(hash((os.getpid(), current_time))) - else: - self._job_id = job_id + def __init__(self, backend: Backend, job_id: str = "local_sim", api_client: APIClient | None = None) -> None: self._backend = backend - self.metadata = kwargs + self._job_id = job_id + self._api_client = api_client + self.set_status(JobStatus.INITIALIZING) + self._result: JobResult | None = None + @property def job_id(self) -> str: - """Return a unique id identifying the job.""" return self._job_id + @property def backend(self) -> Backend: - """Return the backend where this job was executed.""" - if self._backend is None: - msg = "The job does not have any backend." - raise JobError(msg) return self._backend - def done(self) -> bool: - """Return whether the job has successfully run.""" - return self.status() == JobStatus.DONE + def status(self) -> JobStatus: + if self._api_client: + self.set_status(self._api_client.get_job_status(self._job_id)) + else: + # For local simulation, we assume the job is done immediately + self.set_status(JobStatus.DONE) + return self._status - def running(self) -> bool: - """Return whether the job is actively running.""" - return self.status() == JobStatus.RUNNING + def result(self) -> JobResult: + cached_result = self._result + if cached_result is not None: + return cached_result - def cancelled(self) -> bool: - """Return whether the job has been cancelled.""" - return self.status() == JobStatus.CANCELLED + self._wait_for_final_state() - def in_final_state(self) -> bool: - """Return whether the job is in a final job state such as DONE or ERROR.""" - return self.status() in {JobStatus.DONE, JobStatus.ERROR} - - def wait_for_final_state( - self, timeout: float | None = None, wait: float = 5, callback: Callable[[str, str, Job], None] | None = None - ) -> None: - """Poll the job status until it progresses to a final state such as DONE or ERROR. - - Args: - timeout: Seconds to wait for the job. If None, wait indefinitely. - wait: Seconds between queries. - callback: Callback function invoked after each query. - - Raises: - JobTimeoutError: If the job does not reach a final state before the specified timeout. - """ - if not self._async: - return - start_time = time.time() - status = self.status() - while status not in {JobStatus.DONE, JobStatus.ERROR}: - elapsed_time = time.time() - start_time - if timeout is not None and elapsed_time >= timeout: - msg = f"Timeout while waiting for job {self.job_id()}." - raise JobTimeoutError(msg) + if self._api_client: + self._result = self._api_client.get_job_result(self._job_id) + else: + msg = "If the job is not run on the machine, then the result should be given by the simulation already. " + raise RuntimeError(msg) + + return self._result + + def _wait_for_final_state(self, callback: Callable[[str, JobStatus], None] | None = None) -> None: + if self._api_client: + try: + # Using a synchronous wait implementation + self._api_client.wait_for_job_completion(self._job_id, callback) + except Exception as e: + msg = f"Error while waiting for job {self._job_id}: {e!s}" + raise RuntimeError(msg) from e + else: + # For local simulation, we assume the job is done immediately + self.set_status(JobStatus.DONE) if callback: - callback(self.job_id(), status, self) - time.sleep(wait) - status = self.status() + callback(self._job_id, self._status) - def submit(self) -> NoReturn: - """Submit the job to the backend for execution.""" - raise NotImplementedError + def cancelled(self) -> bool: + return self._status == JobStatus.CANCELLED - def result(self) -> JobResult: - """Return the results of the job.""" - return self._result + def done(self) -> bool: + return self._status == JobStatus.DONE + + def running(self) -> bool: + return self._status == JobStatus.RUNNING + + def in_final_state(self) -> bool: + return self._status.is_final def set_result(self, result: JobResult) -> None: self._result = result - def cancel(self) -> NoReturn: - """Attempt to cancel the job.""" - raise NotImplementedError - - def status(self) -> str: - """Return the status of the job, among the values of BackendStatus.""" - raise NotImplementedError + def set_status(self, new_status: JobStatus) -> None: + self._status = new_status diff --git a/src/mqt/qudits/simulation/jobs/job_result.py b/src/mqt/qudits/simulation/jobs/job_result.py index 45c98bee..207c2898 100644 --- a/src/mqt/qudits/simulation/jobs/job_result.py +++ b/src/mqt/qudits/simulation/jobs/job_result.py @@ -1,21 +1,49 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, TypedDict, cast + +import numpy as np +from typing_extensions import NotRequired if TYPE_CHECKING: from collections.abc import Sequence - import numpy as np from numpy.typing import NDArray +class JobResultJSON(TypedDict): + job_id: str # required + state_vector: NotRequired[list[complex]] # optional + counts: NotRequired[list[int]] # optional + + class JobResult: - def __init__(self, state_vector: NDArray[np.complex128], counts: Sequence[int]) -> None: - self.state_vector = state_vector - self.counts = counts + def __init__( + self, + job_id: str | JobResultJSON, + state_vector: NDArray[np.complex128] | None = None, + counts: Sequence[int] | None = None, + ) -> None: + # If first argument is a dict, treat it as JSON data + if isinstance(job_id, dict): + json_data = job_id + self.job_id = json_data.get("job_id", "") + # Convert list to numpy array for state vector + state_vector_data = json_data.get("state_vector", []) + self.state_vector = np.array(state_vector_data, dtype=np.complex128) + self.counts = json_data.get("counts", []) + elif counts is not None and state_vector is not None: + # Traditional initialization with direct parameters + self.job_id = job_id + self.state_vector = state_vector + self.counts = cast("list[int]", counts) def get_counts(self) -> Sequence[int]: return self.counts def get_state_vector(self) -> NDArray[np.complex128]: return self.state_vector + + def get_job_id(self) -> str: + """Return the job ID associated with this result.""" + return self.job_id diff --git a/src/mqt/qudits/simulation/noise_tools/noisy_circuit_factory.py b/src/mqt/qudits/simulation/noise_tools/noisy_circuit_factory.py index 32308b45..59279ae2 100644 --- a/src/mqt/qudits/simulation/noise_tools/noisy_circuit_factory.py +++ b/src/mqt/qudits/simulation/noise_tools/noisy_circuit_factory.py @@ -44,7 +44,8 @@ def generate_circuit(self) -> QuantumCircuit: noisy_circuit.instructions.append(copied_instruction) noisy_circuit.number_gates += 1 - self._apply_noise(noisy_circuit, instruction) + if instruction.is_susceptible: + self._apply_noise(noisy_circuit, instruction) return noisy_circuit diff --git a/src/mqt/qudits/simulation/qudit_provider.py b/src/mqt/qudits/simulation/qudit_provider.py index bad06e35..89431194 100644 --- a/src/mqt/qudits/simulation/qudit_provider.py +++ b/src/mqt/qudits/simulation/qudit_provider.py @@ -5,6 +5,7 @@ from .backends import Innsbruck01, MISim, TNSim from .backends.fake_backends import FakeIonTraps2Six, FakeIonTraps2Trits, FakeIonTraps3Six +from .backends.fake_backends.fake_traps8seven import FakeIonTraps8Seven if TYPE_CHECKING: from .backends.backendv2 import Backend @@ -22,6 +23,7 @@ def version(self) -> int: "faketraps2trits": FakeIonTraps2Trits, "faketraps2six": FakeIonTraps2Six, "faketraps3six": FakeIonTraps3Six, + "faketraps8seven": FakeIonTraps8Seven, } def get_backend(self, name: str | None = None, **kwargs: dict[str, Any]) -> Backend: diff --git a/src/mqt/qudits/visualisation/plot_information.py b/src/mqt/qudits/visualisation/plot_information.py index 1bc688f4..a0974bc0 100644 --- a/src/mqt/qudits/visualisation/plot_information.py +++ b/src/mqt/qudits/visualisation/plot_information.py @@ -19,10 +19,10 @@ def remap_result( ) -> NDArray[np.complex128] | list[int] | NDArray[int]: new_result = np.array(result) if isinstance(result, list) else result.copy() - if circuit.mappings: - permutation = np.eye(circuit.dimensions[0])[:, circuit.mappings[0]] - for i in range(1, len(circuit.mappings)): - permutation = np.kron(permutation, np.eye(circuit.dimensions[i])[:, circuit.mappings[i]]) + if circuit.final_mappings: + permutation = np.eye(circuit.dimensions[0])[:, circuit.final_mappings[0]] + for i in range(1, len(circuit.final_mappings)): + permutation = np.kron(permutation, np.eye(circuit.dimensions[i])[:, circuit.final_mappings[i]]) return new_result @ np.linalg.inv(permutation) return new_result diff --git a/test/python/compiler/compilation_minitools/test_naive_unitary_verifier.py b/test/python/compiler/compilation_minitools/test_naive_unitary_verifier.py index c4a2585a..95139ca7 100644 --- a/test/python/compiler/compilation_minitools/test_naive_unitary_verifier.py +++ b/test/python/compiler/compilation_minitools/test_naive_unitary_verifier.py @@ -1,13 +1,33 @@ from __future__ import annotations +import operator +from functools import reduce +from typing import cast from unittest import TestCase import numpy as np +import pytest from mqt.qudits.compiler.compilation_minitools import UnitaryVerifier +from mqt.qudits.compiler.compilation_minitools.naive_unitary_verifier import ( + mini_phy_unitary_sim, + mini_sim, + mini_unitary_sim, + naive_phy_sim, +) +from mqt.qudits.compiler.twodit.variational_twodit_compilation.sparsifier import ( + random_sparse_unitary, + random_unitary_matrix, +) from mqt.qudits.core import LevelGraph from mqt.qudits.quantum_circuit import QuantumCircuit +rng = np.random.default_rng() + + +def choice(x: list[bool]) -> bool: + return cast("bool", rng.choice(x, size=1)[0]) + class TestUnitaryVerifier(TestCase): def setUp(self) -> None: @@ -61,3 +81,93 @@ def test_verify(self): v1 = UnitaryVerifier(sequence_3, target_3, [dimension], nodes_3, initial_map_3, final_map_3) assert v1.verify() + + @staticmethod + def test_mini_sim(): + circuit = QuantumCircuit(3, [3, 4, 5], 0) + for _k in range(2): + for i in range(3): + for j in range(3): + if i != j: + if choice([True, False]): + circuit.cu_one(i, random_sparse_unitary(circuit.dimensions[i])) + if choice([True, False]): + circuit.cu_two([i, j], random_unitary_matrix(circuit.dimensions[i] * circuit.dimensions[j])) + + size = reduce(operator.mul, circuit.dimensions) + state = np.array(size * [0.0 + 0.0j]) + state[0] = 1.0 + 0.0j + for gate in circuit.instructions: + state = gate.to_matrix(identities=2) @ state + + assert np.allclose(state, mini_sim(circuit)) + + @staticmethod + def test_mini_unitary_sim(): + circuit = QuantumCircuit(3, [3, 4, 5], 0) + for _k in range(2): + for i in range(3): + for j in range(3): + if i != j: + if choice([True, False]): + circuit.cu_one(i, random_sparse_unitary(circuit.dimensions[i])) + if choice([True, False]): + circuit.cu_two([i, j], random_unitary_matrix(circuit.dimensions[i] * circuit.dimensions[j])) + + size = reduce(operator.mul, circuit.dimensions) + id_mat = np.identity(size) + for gate in circuit.instructions: + id_mat = gate.to_matrix(identities=2) @ id_mat + + assert np.allclose(id_mat, mini_unitary_sim(circuit)) + + @staticmethod + def test_naive_phy_sim(): + circuit = QuantumCircuit(3, [3, 4, 5], 0) + for _k in range(2): + for i in range(3): + for j in range(3): + if i != j: + if choice([True, False]): + circuit.cu_one(i, random_sparse_unitary(circuit.dimensions[i])) + if choice([True, False]): + circuit.cu_two([i, j], random_unitary_matrix(circuit.dimensions[i] * circuit.dimensions[j])) + + size = reduce(operator.mul, circuit.dimensions) + state = np.array(size * [0.0 + 0.0j]) + state[0] = 1.0 + 0.0j + for gate in circuit.instructions: + state = gate.to_matrix(identities=2) @ state + + with pytest.raises(AssertionError): + assert np.allclose(state, naive_phy_sim(circuit)) + + circuit.set_initial_mappings([[0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4]]) + circuit.set_final_mappings([[0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4]]) + phstate = naive_phy_sim(circuit) + assert np.allclose(state, phstate) + + @staticmethod + def test_mini_phy_unitary_sim(): + circuit = QuantumCircuit(3, [3, 4, 5], 0) + for _k in range(2): + for i in range(3): + for j in range(3): + if i != j: + if choice([True, False]): + circuit.cu_one(i, random_sparse_unitary(circuit.dimensions[i])) + if choice([True, False]): + circuit.cu_two([i, j], random_unitary_matrix(circuit.dimensions[i] * circuit.dimensions[j])) + + size = reduce(operator.mul, circuit.dimensions) + id_mat = np.identity(size) + for gate in circuit.instructions: + id_mat = gate.to_matrix(identities=2) @ id_mat + + with pytest.raises(AssertionError): + assert np.allclose(id_mat, mini_phy_unitary_sim(circuit)) + + circuit.set_initial_mappings([[0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4]]) + circuit.set_final_mappings([[0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4]]) + phstate = mini_phy_unitary_sim(circuit) + assert np.allclose(id_mat, phstate) diff --git a/test/python/compiler/multi/__init__.py b/test/python/compiler/multi/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/python/compiler/multi/transpile/__init__.py b/test/python/compiler/multi/transpile/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/python/compiler/multi/transpile/test_phy_multi_control_transp.py b/test/python/compiler/multi/transpile/test_phy_multi_control_transp.py new file mode 100644 index 00000000..ef955b3f --- /dev/null +++ b/test/python/compiler/multi/transpile/test_phy_multi_control_transp.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +from unittest import TestCase + +import numpy as np + +from mqt.qudits.compiler import QuditCompiler +from mqt.qudits.compiler.compilation_minitools.naive_unitary_verifier import mini_phy_unitary_sim, mini_unitary_sim +from mqt.qudits.quantum_circuit import QuantumCircuit +from mqt.qudits.simulation import MQTQuditProvider + + +class TestPhyMultiSimplePass(TestCase): + @staticmethod + def test_two_transpile_rctrl(): + # Create the original circuit + circuit = QuantumCircuit(4, [3, 3, 4, 4], 0) + circuit.r(0, [1, 2, np.pi / 3, np.pi / 7]).control([2, 1], [1, 2]) + + # Set up the provider and backend + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps8seven") + + # Compile the circuit + qudit_compiler = QuditCompiler() + passes = ["PhyMultiSimplePass"] + new_circuit = qudit_compiler.compile(backend_ion, circuit, passes) + + uni_l = mini_unitary_sim(circuit) + uni_cl = mini_phy_unitary_sim(new_circuit) + assert np.allclose(uni_l, uni_cl) + + @staticmethod + def test_two_transpile_rzctrl(): + # Create the original circuit + circuit = QuantumCircuit(4, [3, 3, 4, 4], 0) + circuit.rz(1, [1, 2, np.pi / 3]).control([2, 3, 0], [1, 2, 2]) + + # Set up the provider and backend + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps8seven") + + # Compile the circuit + qudit_compiler = QuditCompiler() + passes = ["PhyMultiSimplePass"] + new_circuit = qudit_compiler.compile(backend_ion, circuit, passes) + + uni_l = mini_unitary_sim(circuit).round(10) + uni_cl = mini_phy_unitary_sim(new_circuit).round(10) + assert np.allclose(uni_l, uni_cl) + + @staticmethod + def test_two_transpile_rctrl_close(): + # Create the original circuit + circuit = QuantumCircuit(4, [3, 3, 4, 4], 0) + circuit.r(0, [0, 1, np.pi / 3, np.pi / 7]).control([2, 3, 1], [1, 2, 2]) + + # Set up the provider and backend + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps8seven") + + # Compile the circuit + qudit_compiler = QuditCompiler() + passes = ["PhyMultiSimplePass"] + new_circuit = qudit_compiler.compile(backend_ion, circuit, passes) + + uni_l = mini_unitary_sim(circuit) + uni_cl = mini_phy_unitary_sim(new_circuit) + assert np.allclose(uni_l, uni_cl) + + @staticmethod + def test_two_transpile_rrz_close(): + # Create the original circuit + circuit = QuantumCircuit(3, [3, 4, 4], 0) + + circuit.r(1, [0, 3, np.pi / 3, np.pi / 4]).control([0], [0]) + circuit.r(2, [0, 3, np.pi / 5, np.pi / 5]).control([0, 1], [1, 1]) + circuit.rz(2, [0, 3, np.pi / 5]).control([0, 1], [1, 1]) + # Set up the provider and backend + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps8seven") + + # Compile the circuit + qudit_compiler = QuditCompiler() + passes = ["PhyLocQRPass", "PhyEntSimplePass", "PhyMultiSimplePass"] + new_circuit = qudit_compiler.compile(backend_ion, circuit, passes) + + uni_l = mini_unitary_sim(circuit).round(10) + uni_cl = mini_phy_unitary_sim(new_circuit).round(10) + assert np.allclose(uni_l, uni_cl, rtol=1e-8, atol=1e-8) diff --git a/test/python/compiler/onedit/test_bench_suite.py b/test/python/compiler/onedit/test_bench_suite.py new file mode 100644 index 00000000..17ab1e82 --- /dev/null +++ b/test/python/compiler/onedit/test_bench_suite.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import tempfile +import typing +import unittest +from pathlib import Path + +import numpy as np + +from mqt.qudits.compiler.onedit.randomized_benchmarking.bench_suite import ( + generate_clifford_group, + get_h_gate, + get_package_data_path, + get_s_gate, + load_clifford_group_from_file, + matrix_hash, + save_clifford_group_to_file, +) +from mqt.qudits.quantum_circuit import QuantumCircuit, QuantumRegister + +if typing.TYPE_CHECKING: + from numpy.typing import NDArray + +rng = np.random.default_rng() + + +def randint(x: int, y: int | None = None) -> int: + return rng.integers(0, x) if y is None else rng.integers(x, y) + + +class TestCliffordGroupGeneration(unittest.TestCase): + @staticmethod + def test_get_h_gate(): + h_gate = get_h_gate(2) + expected_h = np.array([[1, 1], [1, -1]]) / np.sqrt(2) + np.testing.assert_array_almost_equal(h_gate, expected_h) + + @staticmethod + def test_get_s_gate(): + s_gate = get_s_gate(2) + expected_s = np.array([[1, 0], [0, 1j]]) + np.testing.assert_array_almost_equal(s_gate, expected_s) + + @staticmethod + def test_matrix_hash(): + matrix1 = np.array([[1, 0], [0, 1]]) + matrix2 = np.array([[1, 0], [0, 1]]) + matrix3 = np.array([[0, 1], [1, 0]]) + + assert matrix_hash(matrix1) == matrix_hash(matrix2) + assert matrix_hash(matrix1) != matrix_hash(matrix3) + + @staticmethod + def test_generate_clifford_group(): + clifford_group = generate_clifford_group(2, max_length=2) + assert len(clifford_group) == 6 + + @staticmethod + def test_get_package_data_path(): + filename = "test_file.pkl" + path = get_package_data_path(filename) + assert path.parent.name == "data" + assert path.name == filename + + @staticmethod + def test_save_and_load_clifford_group(): + clifford_group: dict[str, NDArray] = generate_clifford_group(2, max_length=2) + + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + filename = Path(temp_file.name).name + + try: + save_clifford_group_to_file(clifford_group, filename) + loaded_group = load_clifford_group_from_file(filename) + assert loaded_group is not None + assert len(clifford_group) == len(loaded_group) + for key in clifford_group: # noqa: PLC0206 + np.testing.assert_array_almost_equal(clifford_group[key], loaded_group[key]) + finally: + get_package_data_path(filename).unlink() + + @staticmethod + def test_benching(): + dim_g = 3 + + clifford_group = generate_clifford_group(dim_g, max_length=10) + save_clifford_group_to_file(clifford_group, f"cliffords_{dim_g}.dat") + clifford_group = typing.cast("dict[str, NDArray]", load_clifford_group_from_file(f"cliffords_{dim_g}.dat")) + + def create_rb_sequence(length: int = 2) -> QuantumCircuit: + circuit = QuantumCircuit() + dit_register = QuantumRegister("dits", 1, [3]) + circuit.append(dit_register) + inversion = np.eye(dim_g) + check = np.eye(dim_g) + + for _ in range(length): + random_gate = list(clifford_group.values())[randint(len(clifford_group))] + inversion = np.matmul(inversion, random_gate.conj().T) + circuit.cu_one(0, random_gate) + check = np.matmul(random_gate, check) + circuit.cu_one(0, inversion) + return circuit + + circuit = create_rb_sequence(length=16) + circuit.compileO1("faketraps2trits", "adapt") diff --git a/test/python/compiler/onedit/test_phy_local_adaptive_decomp.py b/test/python/compiler/onedit/test_phy_local_adaptive_decomp.py index 3d1de238..95c20161 100644 --- a/test/python/compiler/onedit/test_phy_local_adaptive_decomp.py +++ b/test/python/compiler/onedit/test_phy_local_adaptive_decomp.py @@ -1,11 +1,17 @@ from __future__ import annotations +from typing import cast from unittest import TestCase +import numpy as np + from mqt.qudits.compiler.compilation_minitools import UnitaryVerifier +from mqt.qudits.compiler.compilation_minitools.naive_unitary_verifier import mini_unitary_sim +from mqt.qudits.compiler.onedit import PhyLocAdaPass, ZPropagationOptPass from mqt.qudits.compiler.onedit.mapping_aware_transpilation import PhyAdaptiveDecomposition, PhyQrDecomp from mqt.qudits.core import LevelGraph from mqt.qudits.quantum_circuit import QuantumCircuit +from mqt.qudits.simulation import MQTQuditProvider class TestPhyLocAdaPass(TestCase): @@ -53,8 +59,51 @@ def test_execute(): v = UnitaryVerifier( matrices_decomposed, htest, [dim], test_sample_nodes, test_sample_nodes_map, final_graph.log_phy_map ) - assert len(matrices_decomposed) == 17 assert v.verify() def test_dfs(self): pass + + @staticmethod + def test_execute_consecutive(): + dim = 3 + c = QuantumCircuit(1, [dim], 0) + circuit_d = QuantumCircuit(1, [dim], 0) + + for _i in range(200): + # r3 = circuit_d.h(0) + r3 = circuit_d.cu_one(0, c.randu([0]).to_matrix()) + + test_circ = circuit_d.copy() + + # Set up the provider and backend + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps2six") + + inimap = backend_ion.energy_level_graphs[0].log_phy_map[:dim] + + phloc = PhyLocAdaPass(backend_ion) + new_circuit = phloc.transpile(circuit_d) + + fmap = phloc.backend.energy_level_graphs[0].log_phy_map[:dim] + + v = UnitaryVerifier(new_circuit.instructions, r3, [dim], list(range(dim)), inimap, fmap) + + uni_l = mini_unitary_sim(circuit_d) + uni = mini_unitary_sim(new_circuit) + tpuni = uni @ v.get_perm_matrix(list(range(dim)), fmap) # Pf + tpuni = v.get_perm_matrix(list(range(dim)), inimap).T @ tpuni # Pi dag + assert np.allclose(tpuni, uni_l, rtol=1e-5, atol=1e-5) + + z_propagation_pass = ZPropagationOptPass(backend=backend_ion, back=False) + new_transpiled_circuit = z_propagation_pass.transpile(new_circuit) + mini_unitary_sim(new_transpiled_circuit).round(4) + tpuni2 = uni @ v.get_perm_matrix(list(range(dim)), fmap) # Pf + tpuni2 = v.get_perm_matrix(list(range(dim)), inimap).T @ tpuni2 # Pi dag + assert np.allclose(tpuni2, uni_l, rtol=1e-5, atol=1e-5) + + adapt_circ = test_circ.compileO1("faketraps2six", "adapt") + u2a = mini_unitary_sim(adapt_circ) + tpuni2a = u2a @ v.get_perm_matrix(list(range(dim)), cast("list[list[int]]", adapt_circ.final_mappings)[0]) # Pf + tpuni2a = v.get_perm_matrix(list(range(dim)), inimap).T @ tpuni2a # Pi dag + assert np.allclose(tpuni2a, uni_l, rtol=1e-5, atol=1e-5) diff --git a/test/python/compiler/onedit/test_propagate_virtrz.py b/test/python/compiler/onedit/test_propagate_virtrz.py index 5d77dc0f..766a1b54 100644 --- a/test/python/compiler/onedit/test_propagate_virtrz.py +++ b/test/python/compiler/onedit/test_propagate_virtrz.py @@ -5,6 +5,7 @@ import numpy as np from mqt.qudits.compiler import QuditCompiler +from mqt.qudits.compiler.compilation_minitools.naive_unitary_verifier import mini_unitary_sim from mqt.qudits.compiler.onedit import ZPropagationOptPass from mqt.qudits.quantum_circuit import QuantumCircuit from mqt.qudits.quantum_circuit.components.quantum_register import QuantumRegister @@ -18,11 +19,11 @@ def setUp(self): self.passes = ["ZPropagationOptPass"] self.backend_ion = provider.get_backend("faketraps2trits") - def test_propagate_z(self): + def test_transpile(self): qreg = QuantumRegister("test_reg", 1, [3]) circ = QuantumCircuit(qreg) - circ.r(qreg[0], [0, 1, np.pi, np.pi / 3]) + circ.r(qreg[0], [0, 1, np.pi, np.pi / 3]).dag() circ.virtrz(qreg[0], [0, np.pi / 3]) circ.r(qreg[0], [0, 1, np.pi, np.pi / 3]) circ.r(qreg[0], [0, 1, np.pi, np.pi / 3]) @@ -33,6 +34,11 @@ def test_propagate_z(self): pass_z = ZPropagationOptPass(backend=self.backend_ion, back=True) new_circuit = pass_z.transpile(circ) + u1 = mini_unitary_sim(circ) + u2 = mini_unitary_sim(new_circuit) + + assert np.allclose(u1, u2) + # VirtZs assert new_circuit.instructions[0].phi == 2 * np.pi / 3 assert new_circuit.instructions[1].phi == 4 * np.pi @@ -53,3 +59,8 @@ def test_propagate_z(self): assert new_circuit.instructions[3].phi == 2 * np.pi / 3 assert new_circuit.instructions[4].phi == 4 * np.pi assert new_circuit.instructions[5].phi == 4 * np.pi + + u1nb = mini_unitary_sim(circ) + u2nb = mini_unitary_sim(new_circuit) + + assert np.allclose(u1nb, u2nb) diff --git a/test/python/compiler/state_compilation/state_preparation.npy b/test/python/compiler/state_compilation/state_preparation.npy new file mode 100644 index 00000000..7115d5d6 Binary files /dev/null and b/test/python/compiler/state_compilation/state_preparation.npy differ diff --git a/test/python/compiler/state_compilation/test_state_preparation.py b/test/python/compiler/state_compilation/test_state_preparation.py index be5e37aa..586c5a7a 100644 --- a/test/python/compiler/state_compilation/test_state_preparation.py +++ b/test/python/compiler/state_compilation/test_state_preparation.py @@ -1,5 +1,6 @@ from __future__ import annotations +from pathlib import Path from unittest import TestCase import numpy as np @@ -8,7 +9,7 @@ from mqt.qudits.compiler.state_compilation.retrieve_state import generate_random_quantum_state, generate_uniform_state from mqt.qudits.compiler.state_compilation.state_preparation import StatePrep from mqt.qudits.compiler.twodit.variational_twodit_compilation.opt.distance_measures import naive_state_fidelity -from mqt.qudits.quantum_circuit import QuantumCircuit +from mqt.qudits.quantum_circuit import QuantumCircuit, QuantumRegister class TestStatePrep(TestCase): @@ -51,3 +52,30 @@ def test_compile_state(): new_circuit = preparation.compile_state() state = mini_sim(new_circuit) assert naive_state_fidelity(state, final_state) > 0.975 + + @staticmethod + def test_state_set_initial_state(): + dimensions = [4, 6, 4, 6, 4] + hilbert_space = QuantumRegister("hilbert_space", len(dimensions), dimensions) + circuit = QuantumCircuit() + circuit.append(hilbert_space) + current_dir = Path(__file__).parent + filef = current_dir / "state_preparation.npy" + psi = np.load(filef) + circuit.set_initial_state(psi) + statesim = circuit.simulate() + assert np.allclose(statesim, psi) + + dimensions_0 = [4, 4, 4] + dimensions_1 = [6, 6] + hilbert_space_0 = QuantumRegister("hilbert_space_0", len(dimensions_0), dimensions_0) + hilbert_space_1 = QuantumRegister("hilbert_space_1", len(dimensions_1), dimensions_1) + circuit_fragments = QuantumCircuit() + circuit_fragments.append(hilbert_space_0) + circuit_fragments.append(hilbert_space_1) + current_dir = Path(__file__).parent + filef = current_dir / "state_preparation.npy" + psi = np.load(filef) + circuit_fragments.set_initial_state(psi) + statesim_f = circuit_fragments.simulate() + assert np.allclose(statesim_f, psi) diff --git a/test/python/compiler/test_dit_compiler.py b/test/python/compiler/test_dit_compiler.py index c5f2af7c..a47130f1 100644 --- a/test/python/compiler/test_dit_compiler.py +++ b/test/python/compiler/test_dit_compiler.py @@ -1,32 +1,215 @@ from __future__ import annotations +import random +from typing import cast from unittest import TestCase import numpy as np from mqt.qudits.compiler import QuditCompiler +from mqt.qudits.compiler.compilation_minitools.naive_unitary_verifier import ( + mini_phy_unitary_sim, + mini_unitary_sim, + naive_phy_sim, +) +from mqt.qudits.compiler.state_compilation.retrieve_state import generate_random_quantum_state +from mqt.qudits.compiler.twodit.variational_twodit_compilation.opt.distance_measures import naive_state_fidelity +from mqt.qudits.compiler.twodit.variational_twodit_compilation.sparsifier import ( + random_sparse_unitary, + random_unitary_matrix, +) from mqt.qudits.quantum_circuit import QuantumCircuit from mqt.qudits.simulation import MQTQuditProvider -from mqt.qudits.visualisation.plot_information import remap_result + +rng = np.random.default_rng() + + +def choice(x: list[bool]) -> bool: + return cast("bool", rng.choice(x, size=1)[0]) class TestQuditCompiler(TestCase): + @staticmethod + def test_compile(): + """!WARNING! + + We are using 1e-6 tolerance due to 17-27k circuit operations on the compiled circuits - + numerical errors compound across this many operations. + Once the compiler methods have been improved the threshold should become tighter! + + """ + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps8seven") + + circuit = QuantumCircuit(3, [3, 4, 5], 0) + for i in range(3): + for j in range(3): + if i != j: + if choice([True, False]): + circuit.cu_one(i, random_sparse_unitary(circuit.dimensions[i])) + if choice([True, False]): + circuit.cu_two([i, j], random_unitary_matrix(circuit.dimensions[i] * circuit.dimensions[j])) + + insts = random.sample(circuit.instructions, len(circuit.instructions)) + circuit.set_instructions(insts) + qudit_compiler = QuditCompiler() + passes = ["PhyLocQRPass", "PhyEntQRCEXPass"] + new_circuit = qudit_compiler.compile(backend_ion, circuit, passes) + + uni_l = mini_unitary_sim(circuit).round(10) + uni_cl = mini_phy_unitary_sim(new_circuit).round(10) + assert np.allclose(uni_l, uni_cl, rtol=1e-6, atol=1e-6) + + og_state = circuit.simulate() + compiled_state = naive_phy_sim(new_circuit) + assert np.allclose(og_state, compiled_state, rtol=1e-6, atol=1e-6) + + @staticmethod + def test_transpile(): + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps8seven") + + final_state = generate_random_quantum_state([3, 4, 5]) + circuit = QuantumCircuit(3, [3, 4, 5], 0) + circuit.set_initial_state(final_state) + + qudit_compiler = QuditCompiler() + passes = ["PhyLocQRPass", "PhyEntSimplePass", "PhyMultiSimplePass"] + new_circuit = qudit_compiler.compile(backend_ion, circuit, passes) + + og_state = circuit.simulate() + compiled_state = naive_phy_sim(new_circuit) + assert np.allclose(og_state, final_state, rtol=1e-6, atol=1e-6) + assert np.allclose(final_state, compiled_state, rtol=1e-6, atol=1e-6) + @staticmethod def test_compile_00(): provider = MQTQuditProvider() - backend_ion = provider.get_backend("faketraps2six") + backend_ion = provider.get_backend("faketraps8seven") + + circuit = QuantumCircuit(3, [3, 4, 5], 0) + for i in range(3): + for j in range(3): + if i != j: + if choice([True, False]): + circuit.cu_one(i, random_sparse_unitary(circuit.dimensions[i])) + if choice([True, False]): + circuit.cu_two([i, j], random_unitary_matrix(circuit.dimensions[i] * circuit.dimensions[j])) + qudit_compiler = QuditCompiler() - circuit_33 = QuantumCircuit(2, [3, 3], 0) - circuit_33.h(0).to_matrix(2).round(2) - circuit_33.r(0, [0, 1, np.pi / 7, np.pi / 3]).to_matrix(0).round(2) - circuit_33.r(0, [1, 2, np.pi / 5, -np.pi / 3]).to_matrix(0).round(2) - circuit_33.x(0).dag().to_matrix(2).round(2) + new_circuit = qudit_compiler.compile_O0(backend_ion, circuit) + + uni_l = mini_unitary_sim(circuit).round(10) + uni_cl = mini_phy_unitary_sim(new_circuit).round(10) + assert np.allclose(uni_l, uni_cl, rtol=1e-6, atol=1e-6) + + og_state = circuit.simulate() + compiled_state = naive_phy_sim(new_circuit) + assert np.allclose(og_state, compiled_state, rtol=1e-6, atol=1e-6) + + @staticmethod + def test_compile_o1_resynth(): + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps8seven") - og_state = circuit_33.simulate().round(5) - ogp = remap_result(og_state, circuit_33) + circuit = QuantumCircuit(3, [3, 4, 5], 0) + for i in range(3): + for j in range(3): + if i != j: + if choice([True, False]): + circuit.cu_one(i, random_sparse_unitary(circuit.dimensions[i])) + if choice([True, False]): + circuit.cu_two([i, j], random_unitary_matrix(circuit.dimensions[i] * circuit.dimensions[j])) + + qudit_compiler = QuditCompiler() + new_circuit = qudit_compiler.compile_O1_resynth(backend_ion, circuit) + + uni_l = mini_unitary_sim(circuit).round(10) + uni_cl = mini_phy_unitary_sim(new_circuit).round(10) + assert np.allclose(uni_l, uni_cl, rtol=1e-6, atol=1e-6) + + og_state = circuit.simulate() + compiled_state = naive_phy_sim(new_circuit) + assert np.allclose(og_state, compiled_state, rtol=1e-6, atol=1e-6) + + @staticmethod + def test_compile_o1_adaptive(): + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps8seven") + + circuit = QuantumCircuit(3, [3, 4, 5], 0) + for i in range(3): + for j in range(3): + if i != j: + if choice([True, False]): + circuit.cu_one(i, random_sparse_unitary(circuit.dimensions[i])) + if choice([True, False]): + circuit.cu_two([i, j], random_unitary_matrix(circuit.dimensions[i] * circuit.dimensions[j])) + + qudit_compiler = QuditCompiler() + new_circuit = qudit_compiler.compile_O1_adaptive(backend_ion, circuit) + + uni_l = mini_unitary_sim(circuit).round(10) + uni_cl = mini_phy_unitary_sim(new_circuit).round(10) + assert np.allclose(uni_l, uni_cl, rtol=1e-6, atol=1e-6) + + og_state = circuit.simulate() + compiled_state = naive_phy_sim(new_circuit) + assert np.allclose(og_state, compiled_state, rtol=1e-6, atol=1e-6) + + @staticmethod + def test_compile_02(): + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps8seven") + + circuit = QuantumCircuit(3, [3, 4, 5], 0) + for _k in range(2): + for i in range(3): + for j in range(3): + if i != j: + if choice([True, False]): + circuit.cu_one(i, random_sparse_unitary(circuit.dimensions[i])) + if choice([True, False]): + circuit.cu_two([i, j], random_unitary_matrix(circuit.dimensions[i] * circuit.dimensions[j])) + + qudit_compiler = QuditCompiler() + new_circuit = qudit_compiler.compile_O2(backend_ion, circuit) + + uni_l = mini_unitary_sim(circuit).round(10) + uni_cl = mini_phy_unitary_sim(new_circuit).round(10) + assert np.allclose(uni_l, uni_cl, rtol=1e-6, atol=1e-6) + + og_state = circuit.simulate() + compiled_state = naive_phy_sim(new_circuit) + assert np.allclose(og_state, compiled_state, rtol=1e-6, atol=1e-6) + + @staticmethod + def test_random_evo_compile(): + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps8seven") + final_state = generate_random_quantum_state([3, 4, 5]) + circuit = QuantumCircuit(3, [3, 4, 5], 0) + circuit.set_initial_state(final_state) + + for _k in range(2): + for i in range(3): + for j in range(3): + if i != j: + if choice([True, False]): + circuit.cu_one(i, random_sparse_unitary(circuit.dimensions[i])) + if choice([True, False]): + circuit.cu_two([i, j], random_unitary_matrix(circuit.dimensions[i] * circuit.dimensions[j])) + + qudit_compiler = QuditCompiler() + new_circuit = qudit_compiler.compile_O2(backend_ion, circuit) - circuit = qudit_compiler.compile_O0(backend_ion, circuit_33) - state = circuit.simulate().round(5) - new_s = remap_result(state, circuit) + uni_l = mini_unitary_sim(circuit).round(10) + uni_cl = mini_phy_unitary_sim(new_circuit).round(10) + assert np.allclose(uni_l, uni_cl, rtol=1e-5, atol=1e-5) - assert np.allclose(new_s, ogp) + og_state = circuit.simulate() + compiled_state = naive_phy_sim(new_circuit) + # assert np.allclose(og_state, compiled_state, rtol=1e-6, atol=1e-6) + og_state = og_state.reshape(-1) + norm_diff_s = naive_state_fidelity(og_state, compiled_state) + assert (1 - norm_diff_s) < 1e-6 diff --git a/test/python/compiler/twodit/entangled_qr/test_crot.py b/test/python/compiler/twodit/entangled_qr/test_crot.py index d8d8973e..844e7250 100644 --- a/test/python/compiler/twodit/entangled_qr/test_crot.py +++ b/test/python/compiler/twodit/entangled_qr/test_crot.py @@ -2,20 +2,32 @@ from unittest import TestCase +import numpy as np + from mqt.qudits.compiler.compilation_minitools.naive_unitary_verifier import mini_unitary_sim from mqt.qudits.compiler.twodit.entanglement_qr import CRotGen from mqt.qudits.quantum_circuit import QuantumCircuit class TestCRot(TestCase): - def setUp(self) -> None: - self.circuit_33 = QuantumCircuit(2, [4, 4], 0) - - def test_crot_101_as_list(self): - self.circuit_33.r(0, [0, 1, 1.0471975511965972, -2.513274122871836]).to_matrix().round(3) - crot_gen = CRotGen(self.circuit_33, [0, 1]) - operations = crot_gen.crot_101_as_list(1.0471975511965972, -2.513274122871836) - p_op = crot_gen.permute_crot_101_as_list(2, 1.0471975511965972, -2.513274122871836) - mini_unitary_sim(self.circuit_33, operations).round(3) - mini_unitary_sim(self.circuit_33, p_op).round(3) - self.circuit_33.r(0, [0, 1, 1.0471975511965972, -2.513274122871836]).to_matrix() + @staticmethod + def test_crot_101_as_list_ctrlop(): + circuit_1 = QuantumCircuit(2, [3, 3], 0) + rm = circuit_1.r(1, [0, 1, 1.0471975511965972, -2.513274122871836]).control([0], [1]).to_matrix(2) + + circuit_2 = QuantumCircuit(2, [3, 3], 0) + crot_gen = CRotGen(circuit_2, [0, 1]) + p_op = crot_gen.permute_crot_101_as_list(3, 1.0471975511965972, -2.513274122871836) + circuit_2.set_instructions(p_op) + assert np.allclose(rm, mini_unitary_sim(circuit_2)) + + @staticmethod + def test_crot_101_as_list_embedded(): + circuit_1 = QuantumCircuit(1, [9], 0) + rm = circuit_1.r(0, [0, 1, 1.0471975511965972, -2.513274122871836]).to_matrix(2) + + circuit_2 = QuantumCircuit(2, [3, 3], 0) + crot_gen = CRotGen(circuit_2, [0, 1]) + p_op = crot_gen.permute_crot_101_as_list(0, 1.0471975511965972, -2.513274122871836) + circuit_2.set_instructions(p_op) + assert np.allclose(rm, mini_unitary_sim(circuit_2)) diff --git a/test/python/compiler/twodit/entangled_qr/test_czrot.py b/test/python/compiler/twodit/entangled_qr/test_czrot.py index 0f936771..c34b9d87 100644 --- a/test/python/compiler/twodit/entangled_qr/test_czrot.py +++ b/test/python/compiler/twodit/entangled_qr/test_czrot.py @@ -10,11 +10,23 @@ class TestCRot(TestCase): - def setUp(self) -> None: - self.circuit_33 = QuantumCircuit(2, [4, 4], 0) + @staticmethod + def test_czrot_101_as_list_ctrlop(): + circuit_1 = QuantumCircuit(2, [3, 3], 0) + rzm = circuit_1.rz(1, [0, 1, np.pi / 4]).control([0], [1]).to_matrix(2) + czrot_gen = CZRotGen(circuit_1, [0, 1]) + p_op = czrot_gen.z_from_crot_101_list(3, np.pi / 4) + circuit_2 = QuantumCircuit(2, [3, 3], 0) + circuit_2.set_instructions(p_op) + assert np.allclose(rzm, mini_unitary_sim(circuit_2)) - def test_crot_101_as_list(self): - self.circuit_33.rz(0, [0, 1, np.pi / 4]).to_matrix().round(3) - czrot_gen = CZRotGen(self.circuit_33, [0, 1]) + @staticmethod + def test_czrot_101_as_list_embedded(): + circuit_1 = QuantumCircuit(1, [9], 0) + rzm = circuit_1.rz(0, [0, 1, np.pi / 4]).to_matrix(2) + + circuit_2 = QuantumCircuit(2, [3, 3], 0) + czrot_gen = CZRotGen(circuit_2, [0, 1]) p_op = czrot_gen.z_from_crot_101_list(0, np.pi / 4) - mini_unitary_sim(self.circuit_33, p_op).round(3) + circuit_2.set_instructions(p_op) + assert np.allclose(rzm, mini_unitary_sim(circuit_2)) diff --git a/test/python/compiler/twodit/entangled_qr/test_entangled_qr.py b/test/python/compiler/twodit/entangled_qr/test_entangled_qr.py index 67db2cfb..846232d2 100644 --- a/test/python/compiler/twodit/entangled_qr/test_entangled_qr.py +++ b/test/python/compiler/twodit/entangled_qr/test_entangled_qr.py @@ -1,56 +1,62 @@ from __future__ import annotations -import typing from unittest import TestCase import numpy as np -from scipy.stats import unitary_group # type: ignore[import-not-found] from mqt.qudits.compiler import QuditCompiler +from mqt.qudits.compiler.compilation_minitools.naive_unitary_verifier import ( + mini_unitary_sim, + naive_phy_sim, +) from mqt.qudits.compiler.twodit.entanglement_qr import EntangledQRCEX +from mqt.qudits.compiler.twodit.variational_twodit_compilation.sparsifier import random_unitary_matrix from mqt.qudits.quantum_circuit import QuantumCircuit from mqt.qudits.simulation import MQTQuditProvider -if typing.TYPE_CHECKING: - from numpy.typing import NDArray - - -def random_unitary_matrix(n: int) -> NDArray[np.complex128, np.complex128]: - return unitary_group.rvs(n) - class TestEntangledQR(TestCase): - def setUp(self) -> None: - MQTQuditProvider() - - self.circuit_53 = QuantumCircuit(2, [5, 3], 0) - self.circuit_s = QuantumCircuit(2, [5, 3], 0) - - def test_entangling_qr(self): - target = random_unitary_matrix(15) + @staticmethod + def test_entangling_qr(): + circuit_53 = QuantumCircuit(2, [5, 3], 0) + target_u = random_unitary_matrix(15) + t = circuit_53.cu_two([0, 1], target_u) - t = self.circuit_53.cu_two([0, 1], target) - MQTQuditProvider() eqr = EntangledQRCEX(t) decomp, _countcr, _countpsw = eqr.execute() + + target = target_u.copy() for rotation in decomp: target = rotation.to_matrix(identities=2) @ target - target /= target[0][0] - res = (abs(target - np.identity(15, dtype="complex")) < 10e-5).all() - assert res + global_phase = target[0][0] + if np.round(global_phase, 13) != 1.0 + 0j: + target /= global_phase + # res = (abs(target - np.identity(15, dtype="complex")) < 10e-13).all() + assert np.allclose(target, np.identity(15, dtype="complex")) + + reconstructed = np.identity(15) + gates_ms = [] + for gate in reversed(decomp): + gates_ms.append((gate, gate.to_matrix(identities=2).conj().T)) + reconstructed = gate.to_matrix(identities=2).conj().T @ reconstructed + + assert np.allclose(reconstructed, target_u) + + for i in range(len(gates_ms)): + gate2check = gates_ms[i][0] + checker = gate2check.dag().to_matrix(identities=2).round(13) + og_conj_t = gates_ms[i][1].round(13) + assert np.allclose(checker, og_conj_t) @staticmethod - def test_entangling_qr_2(): + def test_log_entangling_qr_circuit(): # Create the original circuit - circuit = QuantumCircuit(2, [3, 3], 0) - circuit.x(0) - circuit.csum([0, 1]) + circuit = QuantumCircuit(2, [2, 3], 0) + target = random_unitary_matrix(6) + circuit.cu_two([0, 1], target) # Simulate the original circuit original_state = circuit.simulate() - print("Original circuit simulation result:") - print(original_state.round(3)) - # Set up the provider and backend provider = MQTQuditProvider() backend_ion = provider.get_backend("faketraps2trits") @@ -60,11 +66,39 @@ def test_entangling_qr_2(): passes = ["LogEntQRCEXPass"] new_circuit = qudit_compiler.compile(backend_ion, circuit, passes) + uni_l = mini_unitary_sim(circuit) + uni_cl = mini_unitary_sim(new_circuit) + assert np.allclose(uni_l, uni_cl) + # Simulate the compiled circuit compiled_state = new_circuit.simulate() - print("\nCompiled circuit simulation result:") - print(compiled_state.round(3)) - # Compare the results is_close = np.allclose(original_state, compiled_state) - print(f"\nAre the simulation results close? {is_close}") + assert is_close + + @staticmethod + def test_physical_entangling_qr(): + for _i in range(3): + # Create the original circuit + circuit = QuantumCircuit(3, [3, 4, 7], 0) + circuit.cu_two([0, 1], random_unitary_matrix(12)) + circuit.cu_two([0, 2], random_unitary_matrix(21)) + circuit.cu_two([1, 2], random_unitary_matrix(28)) + circuit.cu_two([0, 2], random_unitary_matrix(21)) + circuit.cu_two([1, 0], random_unitary_matrix(12)) + + # Simulate the original circuit + original_state = circuit.simulate() + + # Set up the provider and backend + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps8seven") + + # Compile the circuit + qudit_compiler = QuditCompiler() + passes = ["PhyEntQRCEXPass"] + new_circuit = qudit_compiler.compile(backend_ion, circuit, passes) + + compiled_state = naive_phy_sim(new_circuit) + assert np.allclose(original_state, compiled_state, rtol=1e-6, atol=1e-6) + assert np.allclose(original_state, compiled_state, rtol=1e-5, atol=1e-5) diff --git a/test/python/compiler/twodit/entangled_qr/test_pswap.py b/test/python/compiler/twodit/entangled_qr/test_pswap.py index aebdc961..28185e37 100644 --- a/test/python/compiler/twodit/entangled_qr/test_pswap.py +++ b/test/python/compiler/twodit/entangled_qr/test_pswap.py @@ -10,17 +10,28 @@ class TestPSwapGen(TestCase): - def setUp(self) -> None: - self.circuit_33 = QuantumCircuit(2, [3, 3], 0) - - def test_pswap_101_as_list(self): - pswap_gen = PSwapGen(self.circuit_33, [0, 1]) - operations_p = pswap_gen.pswap_101_as_list_phases(np.pi / 4, -np.pi / 3) - operations_np = pswap_gen.pswap_101_as_list_no_phases(np.pi / 4, -np.pi / 3) + @staticmethod + def test_pswap_101_as_list_no_phases(): + # Create reference circuit and matrix + circuit_1 = QuantumCircuit(1, [9], 0) + rm = circuit_1.r(0, [2, 3, np.pi / 4, -np.pi / 3]).to_matrix(2) + + # Create test circuit and compare + circuit_2 = QuantumCircuit(2, [3, 3], 0) + pswap_gen = PSwapGen(circuit_2, [0, 1]) + p_op = pswap_gen.pswap_101_as_list_no_phases(np.pi / 4, -np.pi / 3) + circuit_2.set_instructions(p_op) + assert np.allclose(rm, mini_unitary_sim(circuit_2)) + + @staticmethod + def test_permute_pswap_101_as_list(): + # Create reference circuit and matrix + circuit_1 = QuantumCircuit(1, [9], 0) + rm = circuit_1.r(0, [5, 6, np.pi / 4, -np.pi / 3]).to_matrix(2) + + # Create test circuit and compare + circuit_2 = QuantumCircuit(2, [3, 3], 0) + pswap_gen = PSwapGen(circuit_2, [0, 1]) p_op = pswap_gen.permute_pswap_101_as_list(5, np.pi / 4, -np.pi / 3) - - mini_unitary_sim(self.circuit_33, operations_p).round(3) - mini_unitary_sim(self.circuit_33, operations_np).round(3) - mini_unitary_sim(self.circuit_33, p_op).round(3) - - self.circuit_33.r(0, [0, 1, np.pi / 4, -np.pi / 3]).to_matrix() + circuit_2.set_instructions(p_op) + assert np.allclose(rm, mini_unitary_sim(circuit_2)) diff --git a/test/python/compiler/twodit/transpile/__init__.py b/test/python/compiler/twodit/transpile/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/python/compiler/twodit/transpile/test_phy_two_control_trans.py b/test/python/compiler/twodit/transpile/test_phy_two_control_trans.py new file mode 100644 index 00000000..0e8d8da8 --- /dev/null +++ b/test/python/compiler/twodit/transpile/test_phy_two_control_trans.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +from unittest import TestCase + +import numpy as np + +from mqt.qudits.compiler import QuditCompiler +from mqt.qudits.compiler.compilation_minitools.naive_unitary_verifier import mini_phy_unitary_sim, mini_unitary_sim +from mqt.qudits.quantum_circuit import QuantumCircuit +from mqt.qudits.simulation import MQTQuditProvider + + +class TestPhyTwoSimplePass(TestCase): + @staticmethod + def test_two_transpile_rctrl(): + # Create the original circuit + circuit = QuantumCircuit(3, [3, 3, 4], 0) + circuit.r(0, [1, 2, np.pi / 5, np.pi / 7]).control([2], [1]) + circuit.r(0, [1, 2, np.pi / 5, np.pi / 7]).control([2], [1]) + circuit.r(1, [1, 2, np.pi / 5, np.pi / 7]).control([2], [1]) + circuit.r(2, [1, 2, np.pi / 5, np.pi / 7]).control([0], [0]) + + # Set up the provider and backend + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps8seven") + + # Compile the circuit + qudit_compiler = QuditCompiler() + passes = ["PhyEntSimplePass"] + new_circuit = qudit_compiler.compile(backend_ion, circuit, passes) + + uni_l = mini_unitary_sim(circuit) + uni_cl = mini_phy_unitary_sim(new_circuit) + assert np.allclose(uni_l, uni_cl) + + @staticmethod + def test_two_transpile_rzctrl(): + # Create the original circuit + circuit = QuantumCircuit(3, [3, 3, 4], 0) + circuit.rz(0, [0, 1, np.pi / 3]).control([2], [1]) + circuit.rz(1, [1, 2, np.pi / 3]).control([2], [1]) + circuit.rz(2, [0, 3, np.pi / 3]).control([0], [0]) + circuit.rz(2, [0, 3, -np.pi / 3]).control([0], [1]) + circuit.rz(1, [0, 2, -np.pi / 2]).control([0], [2]) + + # Set up the provider and backend + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps8seven") + + # Compile the circuit + qudit_compiler = QuditCompiler() + passes = ["PhyEntSimplePass"] + new_circuit = qudit_compiler.compile(backend_ion, circuit, passes) + + uni_l = mini_unitary_sim(circuit).round(10) + uni_cl = mini_phy_unitary_sim(new_circuit).round(10) + assert np.allclose(uni_l, uni_cl) + + @staticmethod + def test_two_transpile_rctrl_close(): + # Create the original circuit + circuit = QuantumCircuit(3, [3, 3, 4], 0) + circuit.r(0, [0, 1, np.pi / 3, np.pi / 7]).control([1], [1]) + + # Set up the provider and backend + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps8seven") + + # Compile the circuit + qudit_compiler = QuditCompiler() + passes = ["PhyEntSimplePass"] + new_circuit = qudit_compiler.compile(backend_ion, circuit, passes) + + uni_l = mini_unitary_sim(circuit) + uni_cl = mini_phy_unitary_sim(new_circuit) + assert np.allclose(uni_l, uni_cl) + + @staticmethod + def test_two_transpile_cex(): + # Create the original circuit + circuit = QuantumCircuit(3, [3, 3, 4], 0) + circuit.cx([0, 2], [1, 2, 2, np.pi / 7]) + circuit.cx([0, 1], [0, 2, 2, np.pi / 5]) + circuit.cx([1, 2], [1, 3, 1, -np.pi / 7]) + circuit.cx([0, 2], [0, 2, 0, -np.pi / 7]) + + # Set up the provider and backend + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps8seven") + + # Compile the circuit + qudit_compiler = QuditCompiler() + passes = ["PhyEntSimplePass"] + new_circuit = qudit_compiler.compile(backend_ion, circuit, passes) + + uni_l = mini_unitary_sim(circuit) + uni_cl = mini_phy_unitary_sim(new_circuit) + assert np.allclose(uni_l, uni_cl) + + @staticmethod + def test_two_transpile_cex_close(): + # Create the original circuit + circuit = QuantumCircuit(2, [3, 3], 0) + circuit.cx([0, 1], [1, 2, 0, -np.pi / 4]) + + # Set up the provider and backend + provider = MQTQuditProvider() + backend_ion = provider.get_backend("faketraps2trits") + + # Compile the circuit + qudit_compiler = QuditCompiler() + passes = ["PhyEntSimplePass"] + new_circuit = qudit_compiler.compile(backend_ion, circuit, passes) + pi_p = new_circuit.instructions[0] + cex = new_circuit.instructions[1] + pi_pb = new_circuit.instructions[2] + + assert pi_p.lev_a == 0 + assert pi_p.lev_b == 2 + + assert cex.lev_a == 0 + assert cex.lev_b == 1 + + assert pi_pb.lev_a == 0 + assert pi_pb.lev_b == 2 + + uni_l = mini_unitary_sim(circuit) + uni_cl = mini_phy_unitary_sim(new_circuit) + assert np.allclose(uni_l, uni_cl) diff --git a/test/python/compiler/twodit/variational_twodit_compilation/test_sparsifier.py b/test/python/compiler/twodit/variational_twodit_compilation/test_sparsifier.py index 6646aae4..4b6705f7 100644 --- a/test/python/compiler/twodit/variational_twodit_compilation/test_sparsifier.py +++ b/test/python/compiler/twodit/variational_twodit_compilation/test_sparsifier.py @@ -11,13 +11,14 @@ class TestAnsatzSearch(TestCase): def test_sparsify(self) -> None: + self.circuit_bench = QuantumCircuit(2, [3, 3], 0) + x = self.circuit_bench.x(0).to_matrix() self.circuit = QuantumCircuit(2, [3, 3], 0) - x = self.circuit.x(0).to_matrix() check = np.exp(1j * np.pi / 15 * (np.kron(np.eye(3), x) + np.kron(x, np.eye(3)))) sparsity_initial = compute_f(check) u = self.circuit.cu_two([0, 1], check) - circuit = sparsify(u) - op = mini_unitary_sim(self.circuit, circuit.instructions) + sparsify(u) + op = mini_unitary_sim(self.circuit) sparsity_final = compute_f(op) - assert sparsity_final < sparsity_initial + assert sparsity_final <= sparsity_initial diff --git a/test/python/qudits_circuits/gate_set/test_custom_multi.py b/test/python/qudits_circuits/gate_set/test_custom_multi.py index b848ecad..3be2a978 100644 --- a/test/python/qudits_circuits/gate_set/test_custom_multi.py +++ b/test/python/qudits_circuits/gate_set/test_custom_multi.py @@ -4,21 +4,23 @@ import numpy as np +from mqt.qudits.compiler.twodit.variational_twodit_compilation.sparsifier import random_unitary_matrix from mqt.qudits.quantum_circuit import QuantumCircuit class TestCustomMulti(TestCase): @staticmethod def test___array__(): - # All 33 csum circuit_33 = QuantumCircuit(3, [3, 3, 3], 0) - cu = circuit_33.cu_multi([0, 1, 2], 1j * np.identity(27)) + mru = 1j * np.identity(27) * random_unitary_matrix(27) + + cu = circuit_33.cu_multi([0, 1, 2], mru) matrix = cu.to_matrix(identities=0) - assert np.allclose(1j * np.identity(27), matrix) + assert np.allclose(mru, matrix) matrix_dag = cu.dag().to_matrix() - assert np.allclose(-1j * np.identity(27), matrix_dag) + assert np.allclose(mru.conj().T, matrix_dag) @staticmethod def test_validate_parameter(): diff --git a/test/python/qudits_circuits/gate_set/test_custom_one.py b/test/python/qudits_circuits/gate_set/test_custom_one.py index 48ed7da8..f8842e90 100644 --- a/test/python/qudits_circuits/gate_set/test_custom_one.py +++ b/test/python/qudits_circuits/gate_set/test_custom_one.py @@ -4,6 +4,7 @@ import numpy as np +from mqt.qudits.compiler.twodit.variational_twodit_compilation.sparsifier import random_unitary_matrix from mqt.qudits.quantum_circuit import QuantumCircuit @@ -12,13 +13,14 @@ class TestCustomOne(TestCase): def test___array__(): # All 33 csum circuit_33 = QuantumCircuit(2, [3, 3], 0) - cu = circuit_33.cu_one(0, 1j * np.identity(3)) + mru = 1j * np.identity(3) * random_unitary_matrix(3) + cu = circuit_33.cu_one(0, mru) matrix = cu.to_matrix(identities=0) - assert np.allclose(1j * np.identity(3), matrix) + assert np.allclose(mru, matrix) matrix_dag = cu.dag().to_matrix() - assert np.allclose(-1j * np.identity(3), matrix_dag) + assert np.allclose(mru.conj().T, matrix_dag) @staticmethod def test_validate_parameter(): diff --git a/test/python/qudits_circuits/gate_set/test_custom_two.py b/test/python/qudits_circuits/gate_set/test_custom_two.py index 62a39901..2d354375 100644 --- a/test/python/qudits_circuits/gate_set/test_custom_two.py +++ b/test/python/qudits_circuits/gate_set/test_custom_two.py @@ -4,21 +4,23 @@ import numpy as np +from mqt.qudits.compiler.twodit.variational_twodit_compilation.sparsifier import random_unitary_matrix from mqt.qudits.quantum_circuit import QuantumCircuit class TestCustomTwo(TestCase): @staticmethod def test___array__(): - # All 33 csum circuit_33 = QuantumCircuit(2, [3, 3], 0) - cu = circuit_33.cu_two([0, 1], 1j * np.identity(9)) + mru = 1j * np.identity(9) * random_unitary_matrix(9) + + cu = circuit_33.cu_two([0, 1], mru) matrix = cu.to_matrix(identities=0) - assert np.allclose(1j * np.identity(9), matrix) + assert np.allclose(mru, matrix) matrix_dag = cu.dag().to_matrix() - assert np.allclose(-1j * np.identity(9), matrix_dag) + assert np.allclose(mru.conj().T, matrix_dag) @staticmethod def test_validate_parameter(): diff --git a/test/python/qudits_circuits/gate_set/test_cx.py b/test/python/qudits_circuits/gate_set/test_cx.py index 42cce5b3..141a4bee 100644 --- a/test/python/qudits_circuits/gate_set/test_cx.py +++ b/test/python/qudits_circuits/gate_set/test_cx.py @@ -62,13 +62,14 @@ def test___array__(): matrix_dag, ) - # control on 2 but swap 1 and 2, change agle + # control on 2 but swap 1 and 2, change angle circuit_33 = QuantumCircuit(2, [3, 3], 0) cx = circuit_33.cx([0, 1], [1, 2, 2, np.pi / 6]) matrix = cx.to_matrix(identities=0) ang = np.pi / 6 val1 = -1j * np.cos(ang) - np.sin(ang) val2 = -1j * np.cos(ang) + np.sin(ang) + assert np.allclose( np.array([ [1, 0, 0, 0, 0, 0, 0, 0, 0], @@ -212,6 +213,32 @@ def test___array__(): matrix_dag, ) + # control on 2 but swap 1 and 2, change angle + circuit_23 = QuantumCircuit(2, [2, 3], 0) + cx = circuit_23.cx([0, 1], [1, 2, 1, np.pi / 6]) + matrix = cx.to_matrix(identities=0) + ang = np.pi / 6 + val1 = -1j * np.cos(ang) - np.sin(ang) + val2 = -1j * np.cos(ang) + np.sin(ang) + + assert np.allclose( + np.array([ + [1, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, val1], + [0, 0, 0, 0, val2, 0], + ]), + matrix, + ) + + matrix_dag = cx.dag().to_matrix(identities=0) + assert np.allclose( + matrix.conj().T, + matrix_dag, + ) + # all 32 cx circuit_32 = QuantumCircuit(2, [3, 2], 0) diff --git a/test/python/qudits_circuits/gate_set/test_h.py b/test/python/qudits_circuits/gate_set/test_h.py index b42f7b8d..0ef9c484 100644 --- a/test/python/qudits_circuits/gate_set/test_h.py +++ b/test/python/qudits_circuits/gate_set/test_h.py @@ -21,6 +21,9 @@ def test___array__(): h_m = h.to_matrix(identities=0) assert np.allclose(h_m, compare) + matrix_1_dag = h.dag().to_matrix(identities=0) + assert np.allclose(matrix_1_dag, compare.conj().T) + circuit = QuantumCircuit(1, [4], 0) h = circuit.h(0) compare = np.array([ @@ -32,3 +35,6 @@ def test___array__(): compare = compare.round(8) h_m = h.to_matrix(identities=0) assert np.allclose(h_m, compare) + + matrix_1_dag = h.dag().to_matrix(identities=0) + assert np.allclose(matrix_1_dag, compare.conj().T) diff --git a/test/python/qudits_circuits/gate_set/test_perm.py b/test/python/qudits_circuits/gate_set/test_perm.py index d539c7c2..65ef97fc 100644 --- a/test/python/qudits_circuits/gate_set/test_perm.py +++ b/test/python/qudits_circuits/gate_set/test_perm.py @@ -11,7 +11,7 @@ class TestPerm(TestCase): @staticmethod def test___array__(): circuit = QuantumCircuit(2, [6, 2], 0) - ru1 = circuit.pm(0, [0, 2, 1, 5, 3, 4]).to_matrix() + ru1 = circuit.pm(0, [0, 2, 1, 5, 3, 4]) matrix = np.array([ [1, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0], @@ -20,7 +20,10 @@ def test___array__(): [0, 0, 0, 0, 0, 1], [0, 0, 0, 1, 0, 0], ]) - assert np.allclose(ru1, matrix) + assert np.allclose(ru1.to_matrix(), matrix) + + matrix_1_dag = ru1.dag().to_matrix(identities=0) + assert np.allclose(matrix_1_dag, matrix.conj().T) @staticmethod def test_validate_parameter(): diff --git a/test/python/qudits_circuits/gate_set/test_r.py b/test/python/qudits_circuits/gate_set/test_r.py index 667ba1f3..1bbcd13c 100644 --- a/test/python/qudits_circuits/gate_set/test_r.py +++ b/test/python/qudits_circuits/gate_set/test_r.py @@ -1,10 +1,16 @@ from __future__ import annotations +from typing import TYPE_CHECKING, cast from unittest import TestCase import numpy as np from mqt.qudits.quantum_circuit import QuantumCircuit +from mqt.qudits.quantum_circuit.components.extensions.gate_types import GateTypes +from mqt.qudits.quantum_circuit.gates import R + +if TYPE_CHECKING: + from mqt.qudits.quantum_circuit.components.extensions.controls import ControlData class TestR(TestCase): @@ -89,3 +95,21 @@ def test_validate_parameter(): r.validate_parameter([1, 3, np.pi, np.pi / 7]) except AssertionError: assert True + + @staticmethod + def test_control(): + circuit_3 = QuantumCircuit(2, [2, 3], 0) + r = circuit_3.r(0, [1, 0, np.pi, np.pi / 7]).control([1], [1]) + ci = cast("ControlData", r.control_info["controls"]) + assert ci.indices == [1] + assert ci.ctrl_states == [1] + assert r.gate_type == GateTypes.TWO + assert isinstance(r, R) + + circuit_3_2 = QuantumCircuit(3, [2, 3, 3], 0) + r = circuit_3_2.r(0, [1, 0, np.pi, np.pi / 7]).control([1, 2], [1, 1]) + ci = cast("ControlData", r.control_info["controls"]) + assert r.gate_type == GateTypes.MULTI + assert isinstance(r, R) + assert ci.indices == [1, 2] + assert ci.ctrl_states == [1, 1] diff --git a/test/python/qudits_circuits/gate_set/test_rz.py b/test/python/qudits_circuits/gate_set/test_rz.py index f653572d..62b8e41e 100644 --- a/test/python/qudits_circuits/gate_set/test_rz.py +++ b/test/python/qudits_circuits/gate_set/test_rz.py @@ -11,57 +11,58 @@ class TestRz(TestCase): @staticmethod def test___array__(): circuit_3 = QuantumCircuit(1, [3], 0) - vrz = circuit_3.virtrz(0, [1, np.pi / 3]) + rz = circuit_3.rz(0, [1, 2, np.pi / 3]) + # Rz(np.pi / 3, 1, 3) - vrz1_test = np.array([ + rz1_test = np.array([ [1.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j], - [0.0 + 0.0j, 0.5 - 0.8660254j, 0.0 + 0.0j], - [0.0 + 0.0j, 0.0 + 0.0j, 1.0 + 0.0j], + [0.0 + 0.0j, 0.8660254 - 0.5j, 0.0 + 0.0j], + [0.0 + 0.0j, 0.0 + 0.0j, 0.8660254 + 0.5j], ]) - assert np.allclose(vrz.to_matrix(identities=0), vrz1_test) + assert np.allclose(rz.to_matrix(identities=0), rz1_test) - vrz1_test_dag = np.array([ + rz1_test_dag = np.array([ [1.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j], - [0.0 + 0.0j, 0.5 + 0.8660254j, 0.0 + 0.0j], - [0.0 + 0.0j, 0.0 + 0.0j, 1.0 + 0.0j], + [0.0 + 0.0j, 0.8660254 + 0.5j, 0.0 + 0.0j], + [0.0 + 0.0j, 0.0 + 0.0j, 0.8660254 - 0.5j], ]) - assert np.allclose(vrz.dag().to_matrix(identities=0), vrz1_test_dag) + assert np.allclose(rz.dag().to_matrix(identities=0), rz1_test_dag) circuit_4 = QuantumCircuit(1, [4], 0) - vrz = circuit_4.virtrz(0, [1, np.pi / 3]) + rz = circuit_4.rz(0, [1, 3, np.pi / 3]) # Rz(np.pi / 3, 1, 3) - vrz1_test = np.array([ + rz1_test = np.array([ [1.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j], - [0.0 + 0.0j, 0.5 - 0.8660254j, 0.0 + 0.0j, 0.0 + 0.0j], + [0.0 + 0.0j, 0.8660254 - 0.5j, 0.0 + 0.0j, 0.0 + 0.0j], [0.0 + 0.0j, 0.0 + 0.0j, 1.0 + 0.0j, 0.0 + 0.0j], - [0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 1.0 + 0.0j], + [0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.8660254 + 0.5j], ]) - assert np.allclose(vrz.to_matrix(identities=0), vrz1_test) + assert np.allclose(rz.to_matrix(identities=0), rz1_test) - vrz1_test_dag = np.array([ + rz1_test_dag = np.array([ [1.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j], - [0.0 + 0.0j, 0.5 + 0.8660254j, 0.0 + 0.0j, 0.0 + 0.0j], + [0.0 + 0.0j, 0.8660254 + 0.5j, 0.0 + 0.0j, 0.0 + 0.0j], [0.0 + 0.0j, 0.0 + 0.0j, 1.0 + 0.0j, 0.0 + 0.0j], - [0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 1.0 + 0.0j], + [0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.8660254 - 0.5j], ]) - assert np.allclose(vrz.dag().to_matrix(identities=0), vrz1_test_dag) + assert np.allclose(rz.dag().to_matrix(identities=0), rz1_test_dag) @staticmethod def test_regulate_theta(): circuit_4 = QuantumCircuit(1, [4], 0) - vrz = circuit_4.virtrz(0, [1, 0.01 * np.pi]) + rz = circuit_4.rz(0, [1, 2, 0.01 * np.pi]) # Rz(0.01 * np.pi, 1, 4) - assert round(vrz.phi, 4) == 12.5978 + assert round(rz.phi, 4) == 12.5978 @staticmethod def test_cost(): circuit_4 = QuantumCircuit(1, [4], 0) - vrz = circuit_4.virtrz(0, [1, 0.01 * np.pi]) - assert round(vrz.cost, 4) == 0.0004 + rz = circuit_4.virtrz(0, [1, 0.01 * np.pi]) + assert round(rz.cost, 4) == 0.0004 @staticmethod def test_validate_parameter(): diff --git a/test/python/qudits_circuits/gate_set/test_s.py b/test/python/qudits_circuits/gate_set/test_s.py index 6df240b6..12236a1c 100644 --- a/test/python/qudits_circuits/gate_set/test_s.py +++ b/test/python/qudits_circuits/gate_set/test_s.py @@ -21,6 +21,12 @@ def test___array__(self): matrix_0 = s_0.to_matrix(identities=0) assert np.allclose(np.array([[1, 0], [0, 1j]]), matrix_0) + matrix_1_dag = s_0.dag().to_matrix(identities=0) + assert np.allclose(matrix_1_dag, matrix_0.conj().T) + s_1 = self.circuit_23.s(1) matrix_1 = s_1.to_matrix(identities=0) assert np.allclose(np.array([[1, 0, 0], [0, omega_s_d(3), 0], [0, 0, 1]]), matrix_1) + + matrix_1_dag = s_1.dag().to_matrix(identities=0) + assert np.allclose(matrix_1_dag, matrix_1.conj().T) diff --git a/test/python/qudits_circuits/gate_set/test_x.py b/test/python/qudits_circuits/gate_set/test_x.py index b686e8e4..34886f77 100644 --- a/test/python/qudits_circuits/gate_set/test_x.py +++ b/test/python/qudits_circuits/gate_set/test_x.py @@ -16,6 +16,12 @@ def test___array__(self): matrix_0 = x_0.to_matrix(identities=0) assert np.allclose(np.array([[0, 1], [1, 0]]), matrix_0) + matrix_0_dag = x_0.dag().to_matrix(identities=0) + assert np.allclose(matrix_0_dag, matrix_0.conj().T) + x_1 = self.circuit_23.x(1) matrix_1 = x_1.to_matrix(identities=0) assert np.allclose(np.array([[0, 0, 1], [1, 0, 0], [0, 1, 0]]), matrix_1) + + matrix_1_dag = x_1.dag().to_matrix(identities=0) + assert np.allclose(matrix_1_dag, matrix_1.conj().T) diff --git a/test/python/qudits_circuits/gate_set/test_z.py b/test/python/qudits_circuits/gate_set/test_z.py index 1349b23c..80af6814 100644 --- a/test/python/qudits_circuits/gate_set/test_z.py +++ b/test/python/qudits_circuits/gate_set/test_z.py @@ -20,6 +20,12 @@ def test___array__(self): matrix_0 = z_0.to_matrix(identities=0) assert np.allclose(np.array([[1, 0], [0, -1]]), matrix_0) + matrix_0_dag = z_0.dag().to_matrix(identities=0) + assert np.allclose(matrix_0_dag, matrix_0.conj().T) + z_1 = self.circuit_23.z(1) matrix_1 = z_1.to_matrix(identities=0) assert np.allclose(np.array([[1, 0, 0], [0, omega_d(3), 0], [0, 0, (omega_d(3) ** 2)]]), matrix_1) + + matrix_1_dag = z_1.dag().to_matrix(identities=0) + assert np.allclose(matrix_1_dag, matrix_1.conj().T) diff --git a/test/python/qudits_circuits/test_circuit.py b/test/python/qudits_circuits/test_circuit.py index 84c826c9..54a61dce 100644 --- a/test/python/qudits_circuits/test_circuit.py +++ b/test/python/qudits_circuits/test_circuit.py @@ -1,12 +1,26 @@ from __future__ import annotations +from typing import cast from unittest import TestCase import numpy as np +import pytest +from mqt.qudits.compiler.compilation_minitools.naive_unitary_verifier import mini_sim, naive_phy_sim +from mqt.qudits.compiler.state_compilation.retrieve_state import generate_random_quantum_state +from mqt.qudits.compiler.twodit.variational_twodit_compilation.sparsifier import ( + random_sparse_unitary, + random_unitary_matrix, +) from mqt.qudits.quantum_circuit import QuantumCircuit, QuantumRegister from mqt.qudits.quantum_circuit.components import ClassicRegister +rng = np.random.default_rng() + + +def choice(x: list[bool]) -> bool: + return cast("bool", rng.choice(x, size=1)[0]) + class TestQuantumCircuit(TestCase): @staticmethod @@ -41,16 +55,181 @@ def test_to_qasm(): circ.cu_two([qreg_field[0], qreg_matter[1]], np.identity(7 * 2)) circ.cu_multi([qreg_field[0], qreg_matter[1], qreg_matter[0]], np.identity(7 * 2 * 2)) + qasm_program = circ.to_qasm() + expected_ditqasm = ( + "DITQASM 2.0;qreg field [7][7,7,7,7,7,7,7];qreg matter [2][2,2];creg meas[9];" + "x field[0];h matter[0];cx (0, 1, 1, 0.0) field[0], field[1];" + "cx (0, 1, 1, 0.0) field[1], field[2];" + "rxy (0, 1, 3.141592653589793, 1.5707963267948966) matter[1];csum field[2], matter[1];" + "pm (1, 0) matter[0];rh (0, 1) field[2];ls (1.0471975511965976) field[2], matter[0];" + "ms (1.0471975511965976) field[2], matter[0];rz (0, 1, 0.6283185307179586) matter[1];" + "s field[6];virtrz (1, 0.6283185307179586) field[6];z field[4];" + "rdu field[0], matter[0], field[1];" + "cuone (custom_data) field[0];cutwo (custom_data) field[0], matter[1];" + "cumulti (custom_data) field[0], matter[0], matter[1];measure field[0] -> meas[0];" + "measure field[1] -> meas[1];measure field[2] -> meas[2];measure field[3] -> meas[3];" + "measure field[4] -> meas[4];measure field[5] -> meas[5];measure field[6] -> meas[6];" + "measure matter[0] -> meas[7];measure matter[1] -> meas[8];" + ) + + generated_ditqasm = qasm_program.replace("\n", "") + assert generated_ditqasm == expected_ditqasm + + @staticmethod + def test_append(): + qreg_field = QuantumRegister("field", 7, [7, 7]) + QuantumRegister("matter", 2, [2, 2]) + ClassicRegister("classic", 3) + + # Initialize the circuit + with pytest.raises( + IndexError, match="Check your Quantum Register to have the right number of lines and number of dimensions" + ): + QuantumCircuit(qreg_field) + + @staticmethod + def test_save_qasm(): + """Export circuit as QASM program.""" + qreg_field = QuantumRegister("field", 7, [7, 7, 7, 7, 7, 7, 7]) + qreg_matter = QuantumRegister("matter", 2, [2, 2]) + cl_reg = ClassicRegister("classic", 3) + + # Initialize the circuit + circ = QuantumCircuit(qreg_field) + circ.append(qreg_matter) + circ.append_classic(cl_reg) + + # Apply operations + circ.x(qreg_field[0]) + circ.h(qreg_matter[0]) + circ.cx([qreg_field[0], qreg_field[1]]) + circ.cx([qreg_field[1], qreg_field[2]]) + circ.r(qreg_matter[1], [0, 1, np.pi, np.pi / 2]) + circ.csum([qreg_field[2], qreg_matter[1]]) + circ.pm(qreg_matter[0], [1, 0]) + circ.rh(qreg_field[2], [0, 1]) + circ.ls([qreg_field[2], qreg_matter[0]], [np.pi / 3]) + circ.ms([qreg_field[2], qreg_matter[0]], [np.pi / 3]) + circ.rz(qreg_matter[1], [0, 1, np.pi / 5]) + circ.s(qreg_field[6]) + circ.virtrz(qreg_field[6], [1, np.pi / 5]) + circ.z(qreg_field[4]) + circ.randu([qreg_field[0], qreg_matter[0], qreg_field[1]]) + circ.cu_one(qreg_field[0], np.identity(7)) + circ.cu_two([qreg_field[0], qreg_matter[1]], np.identity(7 * 2)) + circ.cu_multi([qreg_field[0], qreg_matter[1], qreg_matter[0]], np.identity(7 * 2 * 2)) + file = circ.save_to_file(file_name="test") circ.to_qasm() circ_new = QuantumCircuit() circ_new.load_from_file(file) - def test_simulate(self): - pass + @staticmethod + def test_simulate(): + final_state = generate_random_quantum_state([3, 4, 5]) + circuit = QuantumCircuit(3, [3, 4, 5], 0) + circuit.set_initial_state(final_state) + + for _k in range(2): + for i in range(3): + for j in range(3): + if i != j: + if choice([True, False]): + circuit.cu_one(i, random_sparse_unitary(circuit.dimensions[i])) + if choice([True, False]): + circuit.cu_two([i, j], random_unitary_matrix(circuit.dimensions[i] * circuit.dimensions[j])) + + og_state = circuit.simulate() + compiled_state = mini_sim(circuit) + assert np.allclose(og_state, compiled_state, rtol=1e-6, atol=1e-6) + + @staticmethod + def test_compileo0(): + final_state = generate_random_quantum_state([3, 4, 5]) + circuit = QuantumCircuit(3, [3, 4, 5], 0) + circuit.set_initial_state(final_state) + + for _k in range(2): + for i in range(3): + for j in range(3): + if i != j: + if choice([True, False]): + circuit.cu_one(i, random_sparse_unitary(circuit.dimensions[i])) + if choice([True, False]): + circuit.cu_two([i, j], random_unitary_matrix(circuit.dimensions[i] * circuit.dimensions[j])) + + compiled_circuit = circuit.compileO0("faketraps8seven") + og_state = circuit.simulate() + compiled_state = naive_phy_sim(compiled_circuit) + assert np.allclose(og_state, compiled_state, rtol=1e-6, atol=1e-6) + + @staticmethod + def test_compileo1_re(): + final_state = generate_random_quantum_state([3, 4, 5]) + circuit = QuantumCircuit(3, [3, 4, 5], 0) + circuit.set_initial_state(final_state) + + for _k in range(2): + for i in range(3): + for j in range(3): + if i != j: + if choice([True, False]): + circuit.cu_one(i, random_sparse_unitary(circuit.dimensions[i])) + if choice([True, False]): + circuit.cu_two([i, j], random_unitary_matrix(circuit.dimensions[i] * circuit.dimensions[j])) + + compiled_circuit = circuit.compileO1("faketraps8seven", "resynth") + og_state = circuit.simulate() + compiled_state = naive_phy_sim(compiled_circuit) + assert np.allclose(og_state, compiled_state, rtol=1e-6, atol=1e-6) + + @staticmethod + def test_compileo1_ada(): + final_state = generate_random_quantum_state([3, 4, 5]) + circuit = QuantumCircuit(3, [3, 4, 5], 0) + circuit.set_initial_state(final_state) - def test_compile(self): - pass + for _k in range(2): + for i in range(3): + for j in range(3): + if i != j: + if choice([True, False]): + circuit.cu_one(i, random_sparse_unitary(circuit.dimensions[i])) + if choice([True, False]): + circuit.cu_two([i, j], random_unitary_matrix(circuit.dimensions[i] * circuit.dimensions[j])) + + compiled_circuit = circuit.compileO1("faketraps8seven", "adapt") + og_state = circuit.simulate() + compiled_state = naive_phy_sim(compiled_circuit) + assert np.allclose(og_state, compiled_state, rtol=1e-6, atol=1e-6) + + @staticmethod + def test_compileo2(): + final_state = generate_random_quantum_state([3, 4, 5]) + circuit = QuantumCircuit(3, [3, 4, 5], 0) + circuit.set_initial_state(final_state) + + for _k in range(2): + for i in range(3): + for j in range(3): + if i != j: + if choice([True, False]): + circuit.cu_one(i, random_sparse_unitary(circuit.dimensions[i])) + if choice([True, False]): + circuit.cu_two([i, j], random_unitary_matrix(circuit.dimensions[i] * circuit.dimensions[j])) + + compiled_circuit = circuit.compileO2("faketraps8seven") + og_state = circuit.simulate() + compiled_state = naive_phy_sim(compiled_circuit) + assert np.allclose(og_state, compiled_state, rtol=1e-6, atol=1e-6) + + @staticmethod + def test_set_initial_state(): + final_state = generate_random_quantum_state([3, 4, 5]) + circuit = QuantumCircuit(3, [3, 4, 5], 0) + circuit.set_initial_state(final_state) - def test_set_initial_state(self): - pass + compiled_circuit = circuit.compileO2("faketraps8seven") + og_state = circuit.simulate() + compiled_state = naive_phy_sim(compiled_circuit) + assert np.allclose(og_state, compiled_state, rtol=1e-6, atol=1e-6) diff --git a/test/python/qudits_circuits/test_qasm.py b/test/python/qudits_circuits/test_qasm.py index ae4f0be1..89d9b420 100644 --- a/test/python/qudits_circuits/test_qasm.py +++ b/test/python/qudits_circuits/test_qasm.py @@ -15,8 +15,8 @@ def test_from_qasm(): qreg matter [2]; creg meas [2]; creg fieldc [7]; - x field[0]; - h matter[0] ctl field[0] field[1] [0,0]; + inv @ x field[0]; + inv @ h matter[0] ctl field[0] field[1] [0,0]; cx (0, 1, 1, pi/2) field[2], matter[0]; cx (1, 2, 0, pi ) field[2], matter[1]; rxy (0, 1, pi, pi/2) field[3]; @@ -25,7 +25,7 @@ def test_from_qasm(): rh (0, 1) field[3]; ls (pi/3) field[2], matter[0]; ms (pi/3) field[5], matter[1]; - rz (0, 1, pi) field[3]; + inv@rz (0, 1, pi) field[3]; s field[6]; virtrz (1, pi/5) field[6]; z field[4]; @@ -60,3 +60,5 @@ def test_from_qasm(): "z", "rdu", ] + assert sum(1 if s.dagger else 0 for s in circuit.instructions) == 3 # checking that there are three dagger + # ops diff --git a/test/python/simulation/test_backends.py b/test/python/simulation/test_backends.py index ae20102a..9705b84a 100644 --- a/test/python/simulation/test_backends.py +++ b/test/python/simulation/test_backends.py @@ -32,7 +32,6 @@ def run_test_on_both_backends(circuit: QuantumCircuit, expected_state: NDArray[n # Compare results from both backends # assert np.allclose(results["misim"], results["tnsim"]), "Results from misim and tnsim do not match" assert True - print("Results from misim and tnsim match.") def test_execute(self): # H gate @@ -220,7 +219,6 @@ def test_tn_long_range(self): # Long range gates for d1 in range(2, 8): for d2 in range(2, 8): - print("Test long range CSUM") qreg_example = QuantumRegister("reg", 4, [d1, 2, d2, 2]) circuit = QuantumCircuit(qreg_example) h = circuit.h(0) @@ -256,7 +254,6 @@ def test_tn_long_range(self): for clev in range(d1): for level_a in range(d2 - 1): for level_b in range(level_a + 1, d2): - print("Test long range CEX") angle = rng.uniform(0, 2 * np.pi) qreg_example = QuantumRegister("reg", 4, [d1, 2, d2, 3]) @@ -275,7 +272,6 @@ def test_tn_long_range(self): for level_a in range(d1 - 1): for level_b in range(level_a + 1, d1): angle = rng.uniform(0, 2 * np.pi) - print("Test long range Cex inverted") qreg_example = QuantumRegister("reg", 4, [d1, 2, d2, 3]) circuit = QuantumCircuit(qreg_example) h = circuit.h(2) diff --git a/test/python/simulation/test_misim.py b/test/python/simulation/test_misim.py index 2b2c51ce..ce1b1fa1 100644 --- a/test/python/simulation/test_misim.py +++ b/test/python/simulation/test_misim.py @@ -252,7 +252,6 @@ def test_tn_long_range(): # Long range gates for d1 in range(2, 8): for d2 in range(2, 8): - print("Test long range CSUM") qreg_example = QuantumRegister("reg", 4, [d1, 2, d2, 2]) circuit = QuantumCircuit(qreg_example) h = circuit.h(0) @@ -296,7 +295,6 @@ def test_tn_long_range(): for clev in range(d1): for level_a in range(d2 - 1): for level_b in range(level_a + 1, d2): - print("Test long range CEX") angle = rng.uniform(0, 2 * np.pi) qreg_example = QuantumRegister("reg", 4, [d1, 2, d2, 3]) @@ -319,7 +317,6 @@ def test_tn_long_range(): for level_a in range(d1 - 1): for level_b in range(level_a + 1, d1): angle = rng.uniform(0, 2 * np.pi) - print("Test long range Cex inverted") qreg_example = QuantumRegister("reg", 4, [d1, 2, d2, 3]) circuit = QuantumCircuit(qreg_example) h = circuit.h(2) diff --git a/test/python/simulation/test_tnsim.py b/test/python/simulation/test_tnsim.py index e48ecf52..25b7ae5e 100644 --- a/test/python/simulation/test_tnsim.py +++ b/test/python/simulation/test_tnsim.py @@ -253,7 +253,6 @@ def test_tn_long_range(): # Long range gates for d1 in range(2, 8): for d2 in range(2, 8): - print("Test long range CSUM") qreg_example = QuantumRegister("reg", 4, [d1, 2, d2, 2]) circuit = QuantumCircuit(qreg_example) h = circuit.h(0) @@ -297,7 +296,6 @@ def test_tn_long_range(): for clev in range(d1): for level_a in range(d2 - 1): for level_b in range(level_a + 1, d2): - print("Test long range CEX") angle = rng.uniform(0, 2 * np.pi) qreg_example = QuantumRegister("reg", 4, [d1, 2, d2, 3]) @@ -320,7 +318,6 @@ def test_tn_long_range(): for level_a in range(d1 - 1): for level_b in range(level_a + 1, d1): angle = rng.uniform(0, 2 * np.pi) - print("Test long range Cex inverted") qreg_example = QuantumRegister("reg", 4, [d1, 2, d2, 3]) circuit = QuantumCircuit(qreg_example) h = circuit.h(2) @@ -383,30 +380,33 @@ def test_stochastic_simulation(): circuit.cx([1, 2], [0, 1, 1, np.pi / 2]).dag() circuit.csum([0, 1]) - # Depolarizing quantum errors - local_error = Noise(probability_depolarizing=0.5, probability_dephasing=0.5) - local_error_rz = Noise(probability_depolarizing=0.5, probability_dephasing=0.5) - entangling_error = Noise(probability_depolarizing=0.5, probability_dephasing=0.5) - entangling_error_extra = Noise(probability_depolarizing=0.5, probability_dephasing=0.5) - entangling_error_on_target = Noise(probability_depolarizing=0.5, probability_dephasing=0.5) - entangling_error_on_control = Noise(probability_depolarizing=0.5, probability_dephasing=0.5) + basic_error = Noise(probability_depolarizing=0.005, probability_dephasing=0.005) + basic_subspace_dynamic_error = SubspaceNoise( + probability_depolarizing=2e-4, probability_dephasing=2e-4, levels=[] + ) - # Add errors to noise_tools model + basic_subspace_dynamic_error_rz = SubspaceNoise( + probability_depolarizing=6e-4, probability_dephasing=4e-4, levels=[] + ) + subspace_error_01 = SubspaceNoise(probability_depolarizing=0.005, probability_dephasing=0.005, levels=(0, 1)) + subspace_error_01_cex = SubspaceNoise( + probability_depolarizing=0.010, probability_dephasing=0.010, levels=(0, 1) + ) + # Add errors to noise_tools model noise_model = NoiseModel() # We know that the architecture is only two qudits - # Very noisy gate - noise_model.add_all_qudit_quantum_error(local_error, ["csum"]) - # Entangling gates - noise_model.add_nonlocal_quantum_error(entangling_error, ["cx", "ls", "ms"]) - noise_model.add_nonlocal_quantum_error_on_target(entangling_error_on_target, ["cx", "ls", "ms"]) - noise_model.add_nonlocal_quantum_error_on_control(entangling_error_on_control, ["csum", "cx", "ls", "ms"]) - # Super noisy Entangling gates - noise_model.add_nonlocal_quantum_error(entangling_error_extra, ["csum"]) - # Local Gates - noise_model.add_quantum_error_locally(local_error, ["rh", "h", "rxy", "s", "x", "z"]) - noise_model.add_quantum_error_locally(local_error_rz, ["rz", "virtrz"]) + # Very noisy gate_matrix + noise_model.add_nonlocal_quantum_error(basic_error, ["csum", "ls"]) + noise_model.add_quantum_error_locally( + basic_error, ["cuone", "cutwo", "cumulti", "h", "perm", "rdu", "s", "x", "z"] + ) + + # Physical gates + noise_model.add_nonlocal_quantum_error(subspace_error_01, ["ms"]) + noise_model.add_nonlocal_quantum_error(subspace_error_01_cex, ["cx"]) + noise_model.add_quantum_error_locally(basic_subspace_dynamic_error, ["rxy", "rh"]) + noise_model.add_quantum_error_locally(basic_subspace_dynamic_error_rz, ["rz"]) - print("Start execution") job = backend.run(circuit, noise_model=noise_model, shots=100) result = job.result() state_vector = result.get_state_vector() @@ -448,7 +448,6 @@ def test_stochastic_simulation_physical(): noise_model.add_quantum_error_locally(sub2, ["rh", "h", "rxy", "x"]) noise_model.add_quantum_error_locally(sub3, ["s"]) - print("Start execution") job = backend.run(circuit, noise_model=noise_model, shots=100) result = job.result() state_vector = result.get_state_vector()