From ed1f94301deea34ad23085293133e519f3fd09df Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Mon, 3 Mar 2025 17:08:00 +0530 Subject: [PATCH 01/20] Implemented Solovay_Kitaev's Algorithm --- src/pyqasm/algorithms/__init__.py | 0 .../algorithms/solovay_kitaev/__init__.py | 0 .../solovay_kitaev/basic_approximation.py | 52 +++++++ .../cache/clifford-t_depth-5.pkl | Bin 0 -> 33928 bytes .../algorithms/solovay_kitaev/generator.py | 102 ++++++++++++ .../algorithms/solovay_kitaev/optimizer.py | 76 +++++++++ .../solovay_kitaev/solovay_kitaev.py | 147 ++++++++++++++++++ src/pyqasm/decomposer.py | 24 ++- src/pyqasm/maps/decomposition_rules.py | 57 +++++++ 9 files changed, 457 insertions(+), 1 deletion(-) create mode 100644 src/pyqasm/algorithms/__init__.py create mode 100644 src/pyqasm/algorithms/solovay_kitaev/__init__.py create mode 100644 src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py create mode 100644 src/pyqasm/algorithms/solovay_kitaev/cache/clifford-t_depth-5.pkl create mode 100644 src/pyqasm/algorithms/solovay_kitaev/generator.py create mode 100644 src/pyqasm/algorithms/solovay_kitaev/optimizer.py create mode 100644 src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py diff --git a/src/pyqasm/algorithms/__init__.py b/src/pyqasm/algorithms/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/pyqasm/algorithms/solovay_kitaev/__init__.py b/src/pyqasm/algorithms/solovay_kitaev/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py new file mode 100644 index 00000000..9b6db3d6 --- /dev/null +++ b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py @@ -0,0 +1,52 @@ +from math import pi +import pickle +import numpy as np + +from pyqasm.elements import BasisSet +import os + + +def basic_approximation(U, target_gate_set, accuracy=0.001, max_tree_depth=10): + current_dir = os.path.dirname(os.path.abspath(__file__)) + gate_set_files = { + BasisSet.CLIFFORD_T: os.path.join(current_dir, "cache", "clifford-t_depth-10.pkl"), + } + + if target_gate_set not in gate_set_files: + raise ValueError(f"Unknown target gate set: {target_gate_set}") + + pkl_file_name = gate_set_files[target_gate_set] + try: + with open(pkl_file_name, "rb") as file: + gate_list = pickle.load(file) + except FileNotFoundError: + raise FileNotFoundError(f"Pickle file not found: {pkl_file_name}") + + closest_gate = None + closest_trace_diff = float("inf") + + for gate in gate_list: + gate_matrix = gate["matrix"] + tree_depth = gate["depth"] + + # Stop if the maximum depth is exceeded + if tree_depth > max_tree_depth: + break + + trace_diff = np.abs(np.trace(np.dot(gate_matrix.conj().T, U) - np.identity(2))) + + if trace_diff < accuracy: + return gate + + # Update the closest gate if the current one is closer + if trace_diff < closest_trace_diff: + closest_trace_diff = trace_diff + closest_gate = gate + + return closest_gate + + +if __name__ == "__main__": + U = np.array([[0.70711, 0.70711j], + [0.70711j, 0.70711]]) + print(basic_approximation(U, BasisSet.CLIFFORD_T, 0.001, 10)) diff --git a/src/pyqasm/algorithms/solovay_kitaev/cache/clifford-t_depth-5.pkl b/src/pyqasm/algorithms/solovay_kitaev/cache/clifford-t_depth-5.pkl new file mode 100644 index 0000000000000000000000000000000000000000..9bd9e6f5bd83421b524d746a5af6c4e31e2c0c3c GIT binary patch literal 33928 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 literal 0 HcmV?d00001 diff --git a/src/pyqasm/algorithms/solovay_kitaev/generator.py b/src/pyqasm/algorithms/solovay_kitaev/generator.py new file mode 100644 index 00000000..edbe80ed --- /dev/null +++ b/src/pyqasm/algorithms/solovay_kitaev/generator.py @@ -0,0 +1,102 @@ +import pickle +import sys +from collections import deque + +import numpy as np + +gate_sets = { + "clifford_T": [ + { + "name": "h", + "identity": { + "group": "h", + "weight": 0.5 + }, + "matrix": (1 / np.sqrt(2)) * np.array([[1, 1], [1, -1]]) + }, + { + "name": "s", + "identity": { + "group": "s-t", + "weight": 0.25 + }, + "matrix": np.array([[1, 0], [0, 1j]]) + }, + { + "name": "t", + "identity": { + "group": "s-t", + "weight": 0.125 + }, + "matrix": np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]), + }, + ] +} + + +def generate_solovay_kitaev_tree_cache(target_gate_set, max_depth, pkl_file_name): + queue = deque([{"name": [], "depth": 0, "matrix": np.eye(2), "identity": {"group": None, "weight": 0}}]) + result = [] + + while queue: + node = queue.popleft() + if node["depth"] == max_depth: + break + + for gate in target_gate_set: + new_group = gate["identity"]["group"] + new_weight = gate["identity"]["weight"] + current_group = node["identity"]["group"] + current_weight = node["identity"]["weight"] + + if current_group != new_group: + new_node = { + "name": node["name"] + [gate["name"]], + "depth": node["depth"] + 1, + "matrix": np.dot(node["matrix"], gate["matrix"]), + "identity": {"group": new_group, "weight": new_weight} + } + queue.append(new_node) + result.append(new_node) + elif current_weight + new_weight < 1: + new_node = { + "name": node["name"] + [gate["name"]], + "depth": node["depth"] + 1, + "matrix": np.dot(node["matrix"], gate["matrix"]), + "identity": {"group": current_group, "weight": current_weight + new_weight} + } + queue.append(new_node) + result.append(new_node) + + print(result) + + with open("cache/" + pkl_file_name, "wb") as f: + pickle.dump(result, f) + + +if __name__ == "__main__": + """ + How to use: + + Run this file direct, and pass the following command line arguments: + + target_gate_set: The target basis set of which you want to generate cached tree. + max_depth: Max depth of the tree which you want to cache. + pkl_file_name: Name of the pickel file in with you want to save the generated cache tree. + + Your command will look like this: + python generator.py + eg.: + python generator.py clifford_T 10 clifford-t_depth-10.pkl + + The file will be saved in cache dir. + """ + + + target_gate_set = sys.argv[1] + max_depth = sys.argv[2] + pkl_file_name = sys.argv[3] + + t = generate_solovay_kitaev_tree_cache( + gate_sets[target_gate_set], int(max_depth), pkl_file_name + ) diff --git a/src/pyqasm/algorithms/solovay_kitaev/optimizer.py b/src/pyqasm/algorithms/solovay_kitaev/optimizer.py new file mode 100644 index 00000000..c20af3ae --- /dev/null +++ b/src/pyqasm/algorithms/solovay_kitaev/optimizer.py @@ -0,0 +1,76 @@ +import numpy as np +from pyqasm.elements import BasisSet + +IDENTITY_WEIGHT_GROUP = { + BasisSet.CLIFFORD_T: { + "h": { + "group": "h", + "weight": 0.5 + }, + "s": { + "group": "s-t", + "weight": 0.25 + }, + "t": { + "group": "s-t", + "weight": 0.125 + }, + "sdg": { + "group": "sdg-tdg", + "weight": 0.25 + }, + "tdg": { + "group": "sdg-tdg", + "weight": 0.125 + }, + } +} + +def optimize_gate_sequnce(seq: list[str], target_basis_set): + target_identity_weight_group = IDENTITY_WEIGHT_GROUP[target_basis_set] + while True: + current_group = None + current_weight = 0 + start_index = 0 + changed = False + + for i, gate_name in enumerate(seq): + gate = target_identity_weight_group[gate_name] + new_group = gate["group"] + new_weight = gate["weight"] + + if current_group is None or new_group != current_group: + current_group = new_group + current_weight = new_weight + start_index = i + else: + current_weight += new_weight + + if current_weight == 1: + seq = seq[:start_index] + seq[i+1:] + changed = True + break + elif current_weight > 1: + remaining_weight = current_weight - 1 + for key, value in target_identity_weight_group.items(): + if value["group"] == current_group and value["weight"] == remaining_weight: + seq = seq[:start_index] + [key] + seq[i+1:] + changed = True + break + break + + if not changed: + return seq + +if __name__ == '__main__': + s1 = ['s', 's', 's', 't', 't', 'tdg', 'sdg', 'sdg', 'sdg', 'tdg', 's', 'h', 's'] + s2 = ['t', 's', 's', 's', 't', 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', 't', 's', 's', 's', 't', 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', 's', 'h', 's'] + s3 = ['h', 's', 's', 't', 't', 's', 't'] # ['h', 't'] + s4 = ['h', 's', 's', 't', 't', 's', 'h'] # [] + s5 = ['h', 's', 's', 't', 'h', 'h', 't', 's', 'h', 't'] # ['t'] + + print(optimize_gate_sequnce(s1, BasisSet.CLIFFORD_T) == ['s', 'h', 's']) + print(optimize_gate_sequnce(s2, BasisSet.CLIFFORD_T) == ['s', 'h', 's']) + print(optimize_gate_sequnce(s3, BasisSet.CLIFFORD_T) == ['h', 't']) + print(optimize_gate_sequnce(s4, BasisSet.CLIFFORD_T) == []) + print(optimize_gate_sequnce(s5, BasisSet.CLIFFORD_T) == ['t']) \ No newline at end of file diff --git a/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py b/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py new file mode 100644 index 00000000..a9a84a33 --- /dev/null +++ b/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py @@ -0,0 +1,147 @@ +import numpy as np +from typing import List, Tuple + +from pyqasm.algorithms.solovay_kitaev.generator import gate_sets +from pyqasm.algorithms.solovay_kitaev.optimizer import optimize_gate_sequnce +from pyqasm.maps.gates import BASIS_GATE_MAP, SELF_INVERTING_ONE_QUBIT_OP_SET, ST_GATE_INV_MAP +from pyqasm.algorithms.solovay_kitaev.basic_approximation import basic_approximation +from pyqasm.elements import BasisSet + +gate_matrix = { + "h": (1 / np.sqrt(2)) * np.array([[1, 1], [1, -1]]), + "s": np.array([[1, 0], [0, 1j]]), + "t": np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]), + "sdg": np.array([[1, 0], [0, 1j]]).conj().T, + "tdg": np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]).conj().T, +} + +class SU2Matrix: + """Class representing a 2x2 Special Unitary matrix.""" + def __init__(self, matrix: np.ndarray, name: List[str]): + self.matrix = matrix + self.name = name + + def __mul__(self, other: 'SU2Matrix') -> 'SU2Matrix': + matrix = np.dot(self.matrix, other.matrix) + name = self.name.copy() + name.extend(other.name) + return SU2Matrix(matrix, name) + + def dagger(self) -> 'SU2Matrix': + """Returns the conjugate transpose.""" + matrix = self.matrix.conj().T + name = [] + for n in self.name[::-1]: + name.append(self._get_dagger_gate_name(n)) + + return SU2Matrix(matrix, name) + + def distance(self, other: 'SU2Matrix') -> float: + """Calculates the operator norm distance between two matrices.""" + diff = self.matrix - other.matrix + return np.linalg.norm(diff) + + def _get_dagger_gate_name(self,name: str): + if name in SELF_INVERTING_ONE_QUBIT_OP_SET: + return name + else: + return ST_GATE_INV_MAP[name] + + def __str__(self): + return f"name: {self.name}, matrix: {self.matrix}" + +def group_commutator(a: SU2Matrix, b: SU2Matrix) -> SU2Matrix: + """Compute the group commutator [a,b] = aba^{-1}b^{-1}.""" + return a * b * a.dagger() * b.dagger() + +def find_basic_approximation(U: SU2Matrix, target_basis_set, accuracy=0.001, max_tree_depth=10) -> SU2Matrix: + gates = basic_approximation(U, target_basis_set, accuracy, max_tree_depth) + return SU2Matrix(gates["matrix"], gates["name"]) + +def decompose_group_element(target: SU2Matrix, target_gate_set, basic_gates: List[SU2Matrix], depth: int) -> Tuple[List[SU2Matrix], float]: + + if depth == 0: + best_approx = find_basic_approximation(target.matrix, target_gate_set) + return best_approx, target.distance(best_approx) + + # Recursive approximation + prev_sequence, prev_error = decompose_group_element(target, target_gate_set, basic_gates, depth - 1) + + # If previous approximation is good enough, return it + # ERROR IS HARD CODED RIGHT NOW -> CHANGE THIS TO FIT USER-INPUT + if prev_error < 1e-6: + return prev_sequence, prev_error + + error = target * prev_sequence.dagger() + + # Find Va and Vb such that their group commutator approximates the error + best_v = None + best_w = None + best_error = float('inf') + + for v in basic_gates: + for w in basic_gates: + comm = group_commutator(v, w) + curr_error = error.distance(comm) + if curr_error < best_error: + best_error = curr_error + best_v = v + best_w = w + + result = prev_sequence + + # Add correction terms + if best_v is not None and best_w is not None: + v_sequence, error = decompose_group_element(best_v, target_gate_set, basic_gates, depth - 1) + w_sequence, error = decompose_group_element(best_w, target_gate_set, basic_gates, depth - 1) + + result = group_commutator(v_sequence, w_sequence) * prev_sequence + + final_error = target.distance(result) + + return result, final_error + +def solovay_kitaev(target: np.ndarray, target_basis_set, depth: int = 3) -> List[np.ndarray]: + """ + Main function to run the Solovay-Kitaev algorithm. + + Args: + target: Target unitary matrix as numpy array + target_basis_set: The target basis set to rebase the module to. + depth: Recursion depth + + Returns: + List of gates that approximate the target unitary + """ + # Convert inputs to SU2Matrix objects + target_su2 = SU2Matrix(target, []) + + target_basis_gate_list = BASIS_GATE_MAP[target_basis_set] + basic_gates_su2 = [SU2Matrix(gate_matrix[gate], [gate]) for gate in target_basis_gate_list if gate != "cx"] + + + # Run the decomposition + sequence, error = decompose_group_element(target_su2, target_basis_set, basic_gates_su2, depth) + + return sequence + # return optimize_gate_sequnce(sequence, target_basis_set) + +if __name__ == '__main__': + U = np.array([[0.70711, 0.70711j], + [0.70711j, 0.70711]]) + + r0 = solovay_kitaev(U, BasisSet.CLIFFORD_T, depth=0) + print(r0.name) # Output: ['s', 'h', 's'] + + r1 = solovay_kitaev(U, BasisSet.CLIFFORD_T, depth=1) + print(r1.name) # Output: ['s', 's', 's', 't', 't', 'tdg', 'sdg', 'sdg', 'sdg', 'tdg', 's', 'h', 's'] + + r2 = solovay_kitaev(U, BasisSet.CLIFFORD_T, depth=2) + print(r2.name) # Output: ['t', 's', 's', 's', 't', 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', 't', 's', 's', 's', 't', 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', 's', 'h', 's'] + + print(np.allclose(r0.matrix, r1.matrix)) # Output: True + print(np.allclose(r1.matrix, r2.matrix)) # Output: True + print(np.allclose(r2.matrix, r0.matrix)) # Output: True + + # Test optimizer + print(optimize_gate_sequnce(r2.name, BasisSet.CLIFFORD_T)) \ No newline at end of file diff --git a/src/pyqasm/decomposer.py b/src/pyqasm/decomposer.py index fd9b337d..b391a97c 100644 --- a/src/pyqasm/decomposer.py +++ b/src/pyqasm/decomposer.py @@ -16,7 +16,8 @@ from openqasm3.ast import BranchingStatement, QuantumGate from pyqasm.exceptions import RebaseError -from pyqasm.maps.decomposition_rules import DECOMPOSITION_RULES, AppliedQubit +from pyqasm.maps.decomposition_rules import DECOMPOSITION_RULES, AppliedQubit, ROTATIONAL_LOOKUP_RULES +from pyqasm.maps.expressions import CONSTANTS_MAP from pyqasm.maps.gates import BASIS_GATE_MAP @@ -38,6 +39,7 @@ def process_gate_statement(cls, gate_name, statement, target_basis_set): list: The processed gates based on the target basis set. """ decomposition_rules = DECOMPOSITION_RULES[target_basis_set] + rotational_lookup_rules = ROTATIONAL_LOOKUP_RULES[target_basis_set] target_basis_gate_list = BASIS_GATE_MAP[target_basis_set] processed_gates_list = [] @@ -51,6 +53,14 @@ def process_gate_statement(cls, gate_name, statement, target_basis_set): decomposition_rules, statement, gate_name ) elif gate_name in {"rx", "ry", "rz"}: + # Use lookup table if ∅ is pi, pi/2 or pi/4 + theta = statement.arguments[0].value + if theta in [CONSTANTS_MAP["pi"], CONSTANTS_MAP["pi"]/2, CONSTANTS_MAP["pi"]/4]: + gate_name = cls._get_rotational_gate_name(gate_name, theta) + processed_gates_list = cls._get_decomposed_gates( + rotational_lookup_rules, statement, gate_name + ) + # Approximate parameterized gates using Solovay-Kitaev # Example - # approx_gates = solovay_kitaev_algo( @@ -159,3 +169,15 @@ def _get_qubits_for_gate(cls, qubits, rule): else: qubits = [qubits[1]] return qubits + + @classmethod + def _get_rotational_gate_name(cls, gate_name, theta): + theta_string = "" + if theta == CONSTANTS_MAP["pi"]: + theta_string = "(pi)" + elif theta == CONSTANTS_MAP["pi"]/2: + theta_string = "(pi)/2" + elif theta == CONSTANTS_MAP["pi"]/4: + theta_string = "(pi)/4" + + return gate_name + theta_string \ No newline at end of file diff --git a/src/pyqasm/maps/decomposition_rules.py b/src/pyqasm/maps/decomposition_rules.py index b2421eb1..4d6c18fb 100644 --- a/src/pyqasm/maps/decomposition_rules.py +++ b/src/pyqasm/maps/decomposition_rules.py @@ -96,6 +96,63 @@ class AppliedQubit(Enum): }, } +ROTATIONAL_LOOKUP_RULES = { + BasisSet.CLIFFORD_T: { + "rz(pi)": [ + {"gate": "s"}, + {"gate": "s"}, + ], + "rz(pi)/2": [ + {"gate": "s"} + ], + "rz(pi)/4": [ + {"gate": "t"} + ], + + # Rx(∅) = H.Rz(∅).H + "rx(pi)": [ + {"gate": "h"}, + {"gate": "s"}, + {"gate": "s"}, + {"gate": "h"}, + ], + "rx(pi)/2": [ + {"gate": "h"}, + {"gate": "s"}, + {"gate": "h"}, + ], + "rx(pi)/4": [ + {"gate": "h"}, + {"gate": "t"}, + {"gate": "h"}, + ], + + # Ry(∅) = S†.H.Rz(∅).H.S + "ry(pi)": [ + {"gate": "sdg"}, + {"gate": "h"}, + {"gate": "s"}, + {"gate": "s"}, + {"gate": "h"}, + {"gate": "s"}, + ], + "ry(pi)/2": [ + {"gate": "sdg"}, + {"gate": "h"}, + {"gate": "s"}, + {"gate": "h"}, + {"gate": "s"}, + ], + "ry(pi)/4": [ + {"gate": "sdg"}, + {"gate": "h"}, + {"gate": "t"}, + {"gate": "h"}, + {"gate": "s"}, + ], + } +} + # """TODO: Implement the Solovay-Kitaev algorithm""" # # def solovay_kitaev_algo(gate_name, param, accuracy): From 95299e4a2fd256a4be442f5bea3728c5ae4f23f1 Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Tue, 4 Mar 2025 11:07:37 +0530 Subject: [PATCH 02/20] Corrected pkl file name in code --- src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py index 9b6db3d6..b67fad1b 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py +++ b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py @@ -9,7 +9,7 @@ def basic_approximation(U, target_gate_set, accuracy=0.001, max_tree_depth=10): current_dir = os.path.dirname(os.path.abspath(__file__)) gate_set_files = { - BasisSet.CLIFFORD_T: os.path.join(current_dir, "cache", "clifford-t_depth-10.pkl"), + BasisSet.CLIFFORD_T: os.path.join(current_dir, "cache", "clifford-t_depth-5.pkl"), } if target_gate_set not in gate_set_files: From 23bcc102c383a417daa178bb11c0b7be5e46df66 Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Tue, 4 Mar 2025 11:22:40 +0530 Subject: [PATCH 03/20] Fixed minor bug due to which test cases were failing. --- src/pyqasm/decomposer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyqasm/decomposer.py b/src/pyqasm/decomposer.py index b391a97c..f8700f8a 100644 --- a/src/pyqasm/decomposer.py +++ b/src/pyqasm/decomposer.py @@ -39,7 +39,6 @@ def process_gate_statement(cls, gate_name, statement, target_basis_set): list: The processed gates based on the target basis set. """ decomposition_rules = DECOMPOSITION_RULES[target_basis_set] - rotational_lookup_rules = ROTATIONAL_LOOKUP_RULES[target_basis_set] target_basis_gate_list = BASIS_GATE_MAP[target_basis_set] processed_gates_list = [] @@ -54,6 +53,7 @@ def process_gate_statement(cls, gate_name, statement, target_basis_set): ) elif gate_name in {"rx", "ry", "rz"}: # Use lookup table if ∅ is pi, pi/2 or pi/4 + rotational_lookup_rules = ROTATIONAL_LOOKUP_RULES[target_basis_set] theta = statement.arguments[0].value if theta in [CONSTANTS_MAP["pi"], CONSTANTS_MAP["pi"]/2, CONSTANTS_MAP["pi"]/4]: gate_name = cls._get_rotational_gate_name(gate_name, theta) From f46de43af2b54cbb5c779483c1fbba3f3f3fd975 Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Thu, 6 Mar 2025 22:12:31 +0530 Subject: [PATCH 04/20] Updated formula for Trace difference --- src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py index b67fad1b..d79be664 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py +++ b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py @@ -33,8 +33,8 @@ def basic_approximation(U, target_gate_set, accuracy=0.001, max_tree_depth=10): if tree_depth > max_tree_depth: break - trace_diff = np.abs(np.trace(np.dot(gate_matrix.conj().T, U) - np.identity(2))) - + # trace_diff = np.abs(np.trace(np.dot(gate_matrix.conj().T, U) - np.identity(2))) + trace_diff = np.linalg.norm(gate_matrix - U, 2) if trace_diff < accuracy: return gate From 77dc71f8257c760dde5b5750635eec48cfe0f24d Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Thu, 6 Mar 2025 22:13:59 +0530 Subject: [PATCH 05/20] Implemented Traversal Basic Approximation --- .../basic_approximation_traversal.py | 156 ++++++++++++++++++ .../experimental/testing_time.py | 56 +++++++ 2 files changed, 212 insertions(+) create mode 100644 src/pyqasm/algorithms/solovay_kitaev/experimental/basic_approximation_traversal.py create mode 100644 src/pyqasm/algorithms/solovay_kitaev/experimental/testing_time.py diff --git a/src/pyqasm/algorithms/solovay_kitaev/experimental/basic_approximation_traversal.py b/src/pyqasm/algorithms/solovay_kitaev/experimental/basic_approximation_traversal.py new file mode 100644 index 00000000..4537ca60 --- /dev/null +++ b/src/pyqasm/algorithms/solovay_kitaev/experimental/basic_approximation_traversal.py @@ -0,0 +1,156 @@ +import pickle +import sys +from collections import deque +from typing import List +import numpy as np +from math import pi + +from pyqasm.elements import BasisSet +from pyqasm.maps.gates import SELF_INVERTING_ONE_QUBIT_OP_SET, ST_GATE_INV_MAP + +gate_sets_info = { + BasisSet.CLIFFORD_T: [ + { + "name": "h", + "identity": { + "group": "h", + "weight": 0.5 + }, + "matrix": (1 / np.sqrt(2)) * np.array([[1, 1], [1, -1]]) + }, + { + "name": "s", + "identity": { + "group": "s-t", + "weight": 0.25 + }, + "matrix": np.array([[1, 0], [0, 1j]]) + }, + { + "name": "t", + "identity": { + "group": "s-t", + "weight": 0.125 + }, + "matrix": np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]), + }, + ] +} + +class TU2Matrix: + def __init__(self, matrix: np.ndarray, name: List[str], identity_group:str, identity_weight:float): + self.matrix = matrix + self.name = name + self.identity_group = identity_group + self.identity_weight = identity_weight + + def __mul__(self, other: 'TU2Matrix') -> 'TU2Matrix': + matrix = np.dot(self.matrix, other.matrix) + name = self.name.copy() + name.extend(other.name) + identity_weight = 0 + identity_group = '' + if self.identity_group == other.identity_group: + identity_weight = self.identity_weight + other.identity_weight + else: + identity_group = other.identity_group + identity_weight = other.identity_weight + + return TU2Matrix(matrix, name, identity_group, identity_weight) + + def can_multiple(self, other: 'TU2Matrix'): + if self.identity_group != other.identity_group: + return True + + if self.identity_weight + other.identity_weight < 1: + return True + + return False + + def get_trace_diff(self, U): + # trace_diff = np.abs(np.trace(np.dot(self.matrix.conj().T, U) - np.identity(2))) + trace_diff = np.linalg.norm(self.matrix - U, 2) + return trace_diff + + def __str__(self): + # return f"name: {self.name}" + return f"name: {self.name}, matrix: {self.matrix}" + + def copy(self) -> 'TU2Matrix': + return TU2Matrix(self.matrix.copy(), self.name.copy(), self.identity_group, self.identity_weight) + +# Constants +best_gate = None +closest_gate = None +closest_trace_diff = float("inf") + +H = TU2Matrix((1 / np.sqrt(2)) * np.array([[1, 1], [1, -1]]), ['h'], 'h', 0.5) +S = TU2Matrix(np.array([[1, 0], [0, 1j]]), ['s'], 's-t', 0.25) +T = TU2Matrix(np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]), ['t'], 's-t', 0.125) + +gate_sets = { + BasisSet.CLIFFORD_T: [H, S, T] +} + +def rescursive_traversal(U, A, target_gate_set_list, current_depth, accuracy=0.001, max_tree_depth=3): + if current_depth >= max_tree_depth: + return + + global closest_trace_diff, closest_gate, best_gate + + if best_gate: + return + + for gate in target_gate_set_list: + if not A.can_multiple(gate): + continue + A_copy = A.copy() + A = A*gate + + trace_diff = A.get_trace_diff(U) + if trace_diff < accuracy: + best_gate = A.copy() + return best_gate + + # Update the closest gate if the current one is closer + if trace_diff < closest_trace_diff: + closest_trace_diff = trace_diff + closest_gate = A.copy() + + # print(A.name) + # if A.name == ['s', 'h', 's']: + # print(A) + # print(trace_diff) + rescursive_traversal(U, A.copy(), target_gate_set_list, current_depth+1, accuracy, max_tree_depth) + A = A_copy.copy() + + pass + +def basic_approximation(U, target_gate_set, accuracy=0.001, max_tree_depth=3): + global closest_trace_diff, closest_gate, best_gate + + A = TU2Matrix(np.identity(2), [], None, None) + target_gate_set_list = gate_sets[target_gate_set] + current_depth = 0 + rescursive_traversal(U, A.copy(), target_gate_set_list,current_depth, accuracy, max_tree_depth) + + result = None + + if best_gate: + result = best_gate.copy() + else: + result = closest_gate.copy() + + # Reset global variables + best_gate = None + closest_gate = None + closest_trace_diff = float("inf") + + return result + + +if __name__ == "__main__": + U = np.array([[0.70711, 0.70711j], + [0.70711j, 0.70711]]) + # basic_approximation(U, BasisSet.CLIFFORD_T, 0.00001, 3) + print(basic_approximation(U, BasisSet.CLIFFORD_T, 0.0001, 3)) diff --git a/src/pyqasm/algorithms/solovay_kitaev/experimental/testing_time.py b/src/pyqasm/algorithms/solovay_kitaev/experimental/testing_time.py new file mode 100644 index 00000000..42852e1e --- /dev/null +++ b/src/pyqasm/algorithms/solovay_kitaev/experimental/testing_time.py @@ -0,0 +1,56 @@ +from pyqasm.algorithms.solovay_kitaev.experimental import basic_approximation_traversal +from pyqasm.algorithms.solovay_kitaev import basic_approximation + +from pyqasm.algorithms.solovay_kitaev.experimental.basic_approximation_traversal import H, S, T + +import numpy as np +from pyqasm.elements import BasisSet + +import timeit +from functools import partial + + +# ['t', 'h', 's', 'h', 't'] +U1 = np.array([[0.5+5.00000000e-01j, 0.70710678-2.29934717e-17j], [0.70710678+0.00000000e+00j, -0.5 +5.00000000e-01j]]) # T*H*S*H*T + +# ['s', 'h', 's'] +U2 = np.array([[0.70711, 0.70711j], + [0.70711j, 0.70711]]) + +# ['h', 't', 'h', 's'] +U3 = (H*T*H*S).matrix + +# ['s', 's', 'h', 't'] +U4 = (S*S*H*T).matrix + +def test_time(U, U_name): + + cache_approach_func = partial(basic_approximation.basic_approximation, U, BasisSet.CLIFFORD_T, 0.001, 5) + traversal_approach_func = partial(basic_approximation_traversal.basic_approximation, U, BasisSet.CLIFFORD_T, 0.001, 5) + + cache_approach_execution_time = timeit.timeit(cache_approach_func, number=1) + traversal_approach_execution_time = timeit.timeit(traversal_approach_func, number=1) + + cache_approach_result = basic_approximation.basic_approximation(U, BasisSet.CLIFFORD_T, 0.001, 5) + traversal_approach_result = basic_approximation_traversal.basic_approximation(U, BasisSet.CLIFFORD_T, 0.001, 5) + + print("-------------------------------------------") + + print(f"U: {U_name}") + + print("**************** Result ****************") + + print(f"Cache approach: {cache_approach_result['name']}") + print(f"Traversal approach: {traversal_approach_result.name}") + + print("**************** Time Taken **************** ") + print(f"Cache approach: {cache_approach_execution_time}") + print(f"Traversal approach: {traversal_approach_execution_time}") + + print("-------------------------------------------") + +if __name__ == '__main__': + test_time(U1, ['t', 'h', 's', 'h', 't']) + test_time(U2, ['s', 'h', 's']) + test_time(U3, ['h', 't', 'h', 's']) + test_time(U4, ['s', 's', 'h', 't']) \ No newline at end of file From ef761436c080d1c8c6d0ecf759826f32e41a493e Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Sun, 9 Mar 2025 12:33:32 +0530 Subject: [PATCH 06/20] Modified rotational_lookup_table to be more human readable --- src/pyqasm/decomposer.py | 16 ++-- src/pyqasm/maps/decomposition_rules.py | 104 +++++++++++++------------ 2 files changed, 63 insertions(+), 57 deletions(-) diff --git a/src/pyqasm/decomposer.py b/src/pyqasm/decomposer.py index f8700f8a..1a81b1f4 100644 --- a/src/pyqasm/decomposer.py +++ b/src/pyqasm/decomposer.py @@ -48,17 +48,18 @@ def process_gate_statement(cls, gate_name, statement, target_basis_set): processed_gates_list = [statement] elif gate_name in decomposition_rules: # Decompose the gates + rule_list = decomposition_rules[gate_name] processed_gates_list = cls._get_decomposed_gates( - decomposition_rules, statement, gate_name + rule_list, statement ) elif gate_name in {"rx", "ry", "rz"}: # Use lookup table if ∅ is pi, pi/2 or pi/4 - rotational_lookup_rules = ROTATIONAL_LOOKUP_RULES[target_basis_set] theta = statement.arguments[0].value if theta in [CONSTANTS_MAP["pi"], CONSTANTS_MAP["pi"]/2, CONSTANTS_MAP["pi"]/4]: - gate_name = cls._get_rotational_gate_name(gate_name, theta) + rotational_lookup_rules = ROTATIONAL_LOOKUP_RULES[target_basis_set] + rule_list = rotational_lookup_rules[gate_name][theta] processed_gates_list = cls._get_decomposed_gates( - rotational_lookup_rules, statement, gate_name + rule_list, statement ) # Approximate parameterized gates using Solovay-Kitaev @@ -118,20 +119,19 @@ def process_branching_statement(cls, branching_statement, target_basis_set): ) @classmethod - def _get_decomposed_gates(cls, decomposition_rules, statement, gate): + def _get_decomposed_gates(cls, rule_list, statement): """Apply the decomposed gates based on the decomposition rules. Args: - decomposition_rules: The decomposition rules to apply. + rule_list: The decomposition rules to apply. statement: The statement to apply the decomposition rules to. - gate: The name of the gate to apply the decomposition rules to. Returns: list: The decomposed gates to be applied. """ decomposed_gates = [] - for rule in decomposition_rules[gate]: + for rule in rule_list: qubits = cls._get_qubits_for_gate(statement.qubits, rule) arguments = [qasm3_ast.FloatLiteral(value=rule["param"])] if "param" in rule else [] diff --git a/src/pyqasm/maps/decomposition_rules.py b/src/pyqasm/maps/decomposition_rules.py index 4d6c18fb..92fac8ec 100644 --- a/src/pyqasm/maps/decomposition_rules.py +++ b/src/pyqasm/maps/decomposition_rules.py @@ -98,58 +98,64 @@ class AppliedQubit(Enum): ROTATIONAL_LOOKUP_RULES = { BasisSet.CLIFFORD_T: { - "rz(pi)": [ - {"gate": "s"}, - {"gate": "s"}, - ], - "rz(pi)/2": [ - {"gate": "s"} - ], - "rz(pi)/4": [ - {"gate": "t"} - ], + "rz": { + CONSTANTS_MAP['pi']: [ + {"gate": "s"}, + {"gate": "s"}, + ], + CONSTANTS_MAP['pi']/2 :[ + {"gate": "s"} + ], + CONSTANTS_MAP['pi']/4 :[ + {"gate": "t"} + ] + }, # Rx(∅) = H.Rz(∅).H - "rx(pi)": [ - {"gate": "h"}, - {"gate": "s"}, - {"gate": "s"}, - {"gate": "h"}, - ], - "rx(pi)/2": [ - {"gate": "h"}, - {"gate": "s"}, - {"gate": "h"}, - ], - "rx(pi)/4": [ - {"gate": "h"}, - {"gate": "t"}, - {"gate": "h"}, - ], - + "rx": { + CONSTANTS_MAP['pi']: [ + {"gate": "h"}, + {"gate": "s"}, + {"gate": "s"}, + {"gate": "h"}, + ], + CONSTANTS_MAP['pi']/2 :[ + {"gate": "h"}, + {"gate": "s"}, + {"gate": "h"}, + ], + CONSTANTS_MAP['pi']/4 :[ + {"gate": "h"}, + {"gate": "t"}, + {"gate": "h"}, + ] + }, + # Ry(∅) = S†.H.Rz(∅).H.S - "ry(pi)": [ - {"gate": "sdg"}, - {"gate": "h"}, - {"gate": "s"}, - {"gate": "s"}, - {"gate": "h"}, - {"gate": "s"}, - ], - "ry(pi)/2": [ - {"gate": "sdg"}, - {"gate": "h"}, - {"gate": "s"}, - {"gate": "h"}, - {"gate": "s"}, - ], - "ry(pi)/4": [ - {"gate": "sdg"}, - {"gate": "h"}, - {"gate": "t"}, - {"gate": "h"}, - {"gate": "s"}, - ], + "ry": { + CONSTANTS_MAP['pi']: [ + {"gate": "sdg"}, + {"gate": "h"}, + {"gate": "s"}, + {"gate": "s"}, + {"gate": "h"}, + {"gate": "s"}, + ], + CONSTANTS_MAP['pi']/2 :[ + {"gate": "sdg"}, + {"gate": "h"}, + {"gate": "s"}, + {"gate": "h"}, + {"gate": "s"}, + ], + CONSTANTS_MAP['pi']/4 :[ + {"gate": "sdg"}, + {"gate": "h"}, + {"gate": "t"}, + {"gate": "h"}, + {"gate": "s"}, + ] + }, } } From 70e3dcc47c733386e36a4ecaa36fbe1f765a9e95 Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Sun, 9 Mar 2025 12:35:26 +0530 Subject: [PATCH 07/20] Removed unwanted method --- src/pyqasm/decomposer.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/pyqasm/decomposer.py b/src/pyqasm/decomposer.py index 1a81b1f4..a569d94d 100644 --- a/src/pyqasm/decomposer.py +++ b/src/pyqasm/decomposer.py @@ -169,15 +169,3 @@ def _get_qubits_for_gate(cls, qubits, rule): else: qubits = [qubits[1]] return qubits - - @classmethod - def _get_rotational_gate_name(cls, gate_name, theta): - theta_string = "" - if theta == CONSTANTS_MAP["pi"]: - theta_string = "(pi)" - elif theta == CONSTANTS_MAP["pi"]/2: - theta_string = "(pi)/2" - elif theta == CONSTANTS_MAP["pi"]/4: - theta_string = "(pi)/4" - - return gate_name + theta_string \ No newline at end of file From 9ddacdedd39dd05d21e003f6aec75ab6afebd3d7 Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Sun, 9 Mar 2025 13:06:30 +0530 Subject: [PATCH 08/20] Worked on comments --- .../algorithms/solovay_kitaev/optimizer.py | 17 ++++++++++------- .../algorithms/solovay_kitaev/solovay_kitaev.py | 10 +++++----- src/pyqasm/maps/gates.py | 1 + 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/pyqasm/algorithms/solovay_kitaev/optimizer.py b/src/pyqasm/algorithms/solovay_kitaev/optimizer.py index c20af3ae..4a2f2e94 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/optimizer.py +++ b/src/pyqasm/algorithms/solovay_kitaev/optimizer.py @@ -26,15 +26,18 @@ } } -def optimize_gate_sequnce(seq: list[str], target_basis_set): +def optimize_gate_sequence(seq: list[str], target_basis_set): target_identity_weight_group = IDENTITY_WEIGHT_GROUP[target_basis_set] - while True: + for _ in range(int(1e6)): current_group = None current_weight = 0 start_index = 0 changed = False for i, gate_name in enumerate(seq): + if gate_name not in target_identity_weight_group: + continue + gate = target_identity_weight_group[gate_name] new_group = gate["group"] new_weight = gate["weight"] @@ -69,8 +72,8 @@ def optimize_gate_sequnce(seq: list[str], target_basis_set): s4 = ['h', 's', 's', 't', 't', 's', 'h'] # [] s5 = ['h', 's', 's', 't', 'h', 'h', 't', 's', 'h', 't'] # ['t'] - print(optimize_gate_sequnce(s1, BasisSet.CLIFFORD_T) == ['s', 'h', 's']) - print(optimize_gate_sequnce(s2, BasisSet.CLIFFORD_T) == ['s', 'h', 's']) - print(optimize_gate_sequnce(s3, BasisSet.CLIFFORD_T) == ['h', 't']) - print(optimize_gate_sequnce(s4, BasisSet.CLIFFORD_T) == []) - print(optimize_gate_sequnce(s5, BasisSet.CLIFFORD_T) == ['t']) \ No newline at end of file + print(optimize_gate_sequence(s1, BasisSet.CLIFFORD_T) == ['s', 'h', 's']) + print(optimize_gate_sequence(s2, BasisSet.CLIFFORD_T) == ['s', 'h', 's']) + print(optimize_gate_sequence(s3, BasisSet.CLIFFORD_T) == ['h', 't']) + print(optimize_gate_sequence(s4, BasisSet.CLIFFORD_T) == []) + print(optimize_gate_sequence(s5, BasisSet.CLIFFORD_T) == ['t']) \ No newline at end of file diff --git a/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py b/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py index a9a84a33..97bc4d7d 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py +++ b/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py @@ -2,8 +2,8 @@ from typing import List, Tuple from pyqasm.algorithms.solovay_kitaev.generator import gate_sets -from pyqasm.algorithms.solovay_kitaev.optimizer import optimize_gate_sequnce -from pyqasm.maps.gates import BASIS_GATE_MAP, SELF_INVERTING_ONE_QUBIT_OP_SET, ST_GATE_INV_MAP +from pyqasm.algorithms.solovay_kitaev.optimizer import optimize_gate_sequence +from pyqasm.maps.gates import BASIS_GATE_MAP, SELF_INVERTING_ONE_QUBIT_OP_SET, ST_GATE_INV_MAP, TWO_QUBIT_OP_SET from pyqasm.algorithms.solovay_kitaev.basic_approximation import basic_approximation from pyqasm.elements import BasisSet @@ -117,14 +117,14 @@ def solovay_kitaev(target: np.ndarray, target_basis_set, depth: int = 3) -> List target_su2 = SU2Matrix(target, []) target_basis_gate_list = BASIS_GATE_MAP[target_basis_set] - basic_gates_su2 = [SU2Matrix(gate_matrix[gate], [gate]) for gate in target_basis_gate_list if gate != "cx"] + basic_gates_su2 = [SU2Matrix(gate_matrix[gate], [gate]) for gate in target_basis_gate_list if gate not in TWO_QUBIT_OP_SET] # Run the decomposition sequence, error = decompose_group_element(target_su2, target_basis_set, basic_gates_su2, depth) return sequence - # return optimize_gate_sequnce(sequence, target_basis_set) + # return optimize_gate_sequence(sequence, target_basis_set) if __name__ == '__main__': U = np.array([[0.70711, 0.70711j], @@ -144,4 +144,4 @@ def solovay_kitaev(target: np.ndarray, target_basis_set, depth: int = 3) -> List print(np.allclose(r2.matrix, r0.matrix)) # Output: True # Test optimizer - print(optimize_gate_sequnce(r2.name, BasisSet.CLIFFORD_T)) \ No newline at end of file + print(optimize_gate_sequence(r2.name, BasisSet.CLIFFORD_T)) \ No newline at end of file diff --git a/src/pyqasm/maps/gates.py b/src/pyqasm/maps/gates.py index da27173c..b6f2d262 100644 --- a/src/pyqasm/maps/gates.py +++ b/src/pyqasm/maps/gates.py @@ -1158,6 +1158,7 @@ def map_qasm_op_to_callable(op_name: str) -> tuple[Callable, int]: "U2": u2_inv_gate, "u2": u2_inv_gate, } +TWO_QUBIT_OP_SET = {"cx", "cz", "swap"} def map_qasm_inv_op_to_callable(op_name: str): From 08934547b0b4d022c779ae429782cce77d21f0c7 Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Sun, 9 Mar 2025 17:05:19 +0530 Subject: [PATCH 09/20] Changed code structure and use traversal approch for basic approximation --- .../solovay_kitaev/basic_approximation.py | 99 ++++++----- .../cache/clifford-t_depth-5.pkl | Bin 33928 -> 0 bytes .../basic_approximation_traversal.py | 156 ------------------ .../experimental/testing_time.py | 56 ------- .../algorithms/solovay_kitaev/generator.py | 102 ------------ .../solovay_kitaev/solovay_kitaev.py | 79 ++------- src/pyqasm/algorithms/solovay_kitaev/utils.py | 103 ++++++++++++ src/pyqasm/maps/gates.py | 50 +++++- 8 files changed, 230 insertions(+), 415 deletions(-) delete mode 100644 src/pyqasm/algorithms/solovay_kitaev/cache/clifford-t_depth-5.pkl delete mode 100644 src/pyqasm/algorithms/solovay_kitaev/experimental/basic_approximation_traversal.py delete mode 100644 src/pyqasm/algorithms/solovay_kitaev/experimental/testing_time.py delete mode 100644 src/pyqasm/algorithms/solovay_kitaev/generator.py create mode 100644 src/pyqasm/algorithms/solovay_kitaev/utils.py diff --git a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py index d79be664..49954613 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py +++ b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py @@ -1,52 +1,73 @@ -from math import pi -import pickle import numpy as np from pyqasm.elements import BasisSet -import os +from pyqasm.algorithms.solovay_kitaev.utils import TU2Matrix, get_TU2Matrix_for_basic_approximation -def basic_approximation(U, target_gate_set, accuracy=0.001, max_tree_depth=10): - current_dir = os.path.dirname(os.path.abspath(__file__)) - gate_set_files = { - BasisSet.CLIFFORD_T: os.path.join(current_dir, "cache", "clifford-t_depth-5.pkl"), - } - - if target_gate_set not in gate_set_files: - raise ValueError(f"Unknown target gate set: {target_gate_set}") - - pkl_file_name = gate_set_files[target_gate_set] - try: - with open(pkl_file_name, "rb") as file: - gate_list = pickle.load(file) - except FileNotFoundError: - raise FileNotFoundError(f"Pickle file not found: {pkl_file_name}") - - closest_gate = None - closest_trace_diff = float("inf") - - for gate in gate_list: - gate_matrix = gate["matrix"] - tree_depth = gate["depth"] - - # Stop if the maximum depth is exceeded - if tree_depth > max_tree_depth: - break - - # trace_diff = np.abs(np.trace(np.dot(gate_matrix.conj().T, U) - np.identity(2))) - trace_diff = np.linalg.norm(gate_matrix - U, 2) - if trace_diff < accuracy: - return gate +# Constants +best_gate = None +closest_gate = None +closest_diff = float("inf") +def rescursive_traversal(U, A, target_gate_set_list, current_depth, accuracy=0.001, max_tree_depth=3): + if current_depth >= max_tree_depth: + return + + global closest_diff, closest_gate, best_gate + + if best_gate: + return + + for gate in target_gate_set_list: + if not A.can_multiple(gate): + continue + A_copy = A.copy() + A = A*gate + + diff = A.get_diff(U) + if diff < accuracy: + best_gate = A.copy() + return best_gate + # Update the closest gate if the current one is closer - if trace_diff < closest_trace_diff: - closest_trace_diff = trace_diff - closest_gate = gate + if diff < closest_diff: + closest_diff = diff + closest_gate = A.copy() + + # print(A.name) + # if A.name == ['s', 'h', 's']: + # print(A) + # print(diff) + rescursive_traversal(U, A.copy(), target_gate_set_list, current_depth+1, accuracy, max_tree_depth) + A = A_copy.copy() + + pass - return closest_gate +def basic_approximation(U, target_gate_set, accuracy=0.001, max_tree_depth=3): + global closest_diff, closest_gate, best_gate + + A = TU2Matrix(np.identity(2), [], None, None) + target_gate_set_list = get_TU2Matrix_for_basic_approximation(target_gate_set) + current_depth = 0 + rescursive_traversal(U, A.copy(), target_gate_set_list,current_depth, accuracy, max_tree_depth) + + result = None + + if best_gate: + result = best_gate.copy() + else: + result = closest_gate.copy() + + # Reset global variables + best_gate = None + closest_gate = None + closest_diff = float("inf") + + return result if __name__ == "__main__": U = np.array([[0.70711, 0.70711j], [0.70711j, 0.70711]]) - print(basic_approximation(U, BasisSet.CLIFFORD_T, 0.001, 10)) + + print(basic_approximation(U, BasisSet.CLIFFORD_T, 0.0001, 3)) diff --git a/src/pyqasm/algorithms/solovay_kitaev/cache/clifford-t_depth-5.pkl b/src/pyqasm/algorithms/solovay_kitaev/cache/clifford-t_depth-5.pkl deleted file mode 100644 index 9bd9e6f5bd83421b524d746a5af6c4e31e2c0c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33928 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 diff --git a/src/pyqasm/algorithms/solovay_kitaev/experimental/basic_approximation_traversal.py b/src/pyqasm/algorithms/solovay_kitaev/experimental/basic_approximation_traversal.py deleted file mode 100644 index 4537ca60..00000000 --- a/src/pyqasm/algorithms/solovay_kitaev/experimental/basic_approximation_traversal.py +++ /dev/null @@ -1,156 +0,0 @@ -import pickle -import sys -from collections import deque -from typing import List -import numpy as np -from math import pi - -from pyqasm.elements import BasisSet -from pyqasm.maps.gates import SELF_INVERTING_ONE_QUBIT_OP_SET, ST_GATE_INV_MAP - -gate_sets_info = { - BasisSet.CLIFFORD_T: [ - { - "name": "h", - "identity": { - "group": "h", - "weight": 0.5 - }, - "matrix": (1 / np.sqrt(2)) * np.array([[1, 1], [1, -1]]) - }, - { - "name": "s", - "identity": { - "group": "s-t", - "weight": 0.25 - }, - "matrix": np.array([[1, 0], [0, 1j]]) - }, - { - "name": "t", - "identity": { - "group": "s-t", - "weight": 0.125 - }, - "matrix": np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]), - }, - ] -} - -class TU2Matrix: - def __init__(self, matrix: np.ndarray, name: List[str], identity_group:str, identity_weight:float): - self.matrix = matrix - self.name = name - self.identity_group = identity_group - self.identity_weight = identity_weight - - def __mul__(self, other: 'TU2Matrix') -> 'TU2Matrix': - matrix = np.dot(self.matrix, other.matrix) - name = self.name.copy() - name.extend(other.name) - identity_weight = 0 - identity_group = '' - if self.identity_group == other.identity_group: - identity_weight = self.identity_weight + other.identity_weight - else: - identity_group = other.identity_group - identity_weight = other.identity_weight - - return TU2Matrix(matrix, name, identity_group, identity_weight) - - def can_multiple(self, other: 'TU2Matrix'): - if self.identity_group != other.identity_group: - return True - - if self.identity_weight + other.identity_weight < 1: - return True - - return False - - def get_trace_diff(self, U): - # trace_diff = np.abs(np.trace(np.dot(self.matrix.conj().T, U) - np.identity(2))) - trace_diff = np.linalg.norm(self.matrix - U, 2) - return trace_diff - - def __str__(self): - # return f"name: {self.name}" - return f"name: {self.name}, matrix: {self.matrix}" - - def copy(self) -> 'TU2Matrix': - return TU2Matrix(self.matrix.copy(), self.name.copy(), self.identity_group, self.identity_weight) - -# Constants -best_gate = None -closest_gate = None -closest_trace_diff = float("inf") - -H = TU2Matrix((1 / np.sqrt(2)) * np.array([[1, 1], [1, -1]]), ['h'], 'h', 0.5) -S = TU2Matrix(np.array([[1, 0], [0, 1j]]), ['s'], 's-t', 0.25) -T = TU2Matrix(np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]), ['t'], 's-t', 0.125) - -gate_sets = { - BasisSet.CLIFFORD_T: [H, S, T] -} - -def rescursive_traversal(U, A, target_gate_set_list, current_depth, accuracy=0.001, max_tree_depth=3): - if current_depth >= max_tree_depth: - return - - global closest_trace_diff, closest_gate, best_gate - - if best_gate: - return - - for gate in target_gate_set_list: - if not A.can_multiple(gate): - continue - A_copy = A.copy() - A = A*gate - - trace_diff = A.get_trace_diff(U) - if trace_diff < accuracy: - best_gate = A.copy() - return best_gate - - # Update the closest gate if the current one is closer - if trace_diff < closest_trace_diff: - closest_trace_diff = trace_diff - closest_gate = A.copy() - - # print(A.name) - # if A.name == ['s', 'h', 's']: - # print(A) - # print(trace_diff) - rescursive_traversal(U, A.copy(), target_gate_set_list, current_depth+1, accuracy, max_tree_depth) - A = A_copy.copy() - - pass - -def basic_approximation(U, target_gate_set, accuracy=0.001, max_tree_depth=3): - global closest_trace_diff, closest_gate, best_gate - - A = TU2Matrix(np.identity(2), [], None, None) - target_gate_set_list = gate_sets[target_gate_set] - current_depth = 0 - rescursive_traversal(U, A.copy(), target_gate_set_list,current_depth, accuracy, max_tree_depth) - - result = None - - if best_gate: - result = best_gate.copy() - else: - result = closest_gate.copy() - - # Reset global variables - best_gate = None - closest_gate = None - closest_trace_diff = float("inf") - - return result - - -if __name__ == "__main__": - U = np.array([[0.70711, 0.70711j], - [0.70711j, 0.70711]]) - # basic_approximation(U, BasisSet.CLIFFORD_T, 0.00001, 3) - print(basic_approximation(U, BasisSet.CLIFFORD_T, 0.0001, 3)) diff --git a/src/pyqasm/algorithms/solovay_kitaev/experimental/testing_time.py b/src/pyqasm/algorithms/solovay_kitaev/experimental/testing_time.py deleted file mode 100644 index 42852e1e..00000000 --- a/src/pyqasm/algorithms/solovay_kitaev/experimental/testing_time.py +++ /dev/null @@ -1,56 +0,0 @@ -from pyqasm.algorithms.solovay_kitaev.experimental import basic_approximation_traversal -from pyqasm.algorithms.solovay_kitaev import basic_approximation - -from pyqasm.algorithms.solovay_kitaev.experimental.basic_approximation_traversal import H, S, T - -import numpy as np -from pyqasm.elements import BasisSet - -import timeit -from functools import partial - - -# ['t', 'h', 's', 'h', 't'] -U1 = np.array([[0.5+5.00000000e-01j, 0.70710678-2.29934717e-17j], [0.70710678+0.00000000e+00j, -0.5 +5.00000000e-01j]]) # T*H*S*H*T - -# ['s', 'h', 's'] -U2 = np.array([[0.70711, 0.70711j], - [0.70711j, 0.70711]]) - -# ['h', 't', 'h', 's'] -U3 = (H*T*H*S).matrix - -# ['s', 's', 'h', 't'] -U4 = (S*S*H*T).matrix - -def test_time(U, U_name): - - cache_approach_func = partial(basic_approximation.basic_approximation, U, BasisSet.CLIFFORD_T, 0.001, 5) - traversal_approach_func = partial(basic_approximation_traversal.basic_approximation, U, BasisSet.CLIFFORD_T, 0.001, 5) - - cache_approach_execution_time = timeit.timeit(cache_approach_func, number=1) - traversal_approach_execution_time = timeit.timeit(traversal_approach_func, number=1) - - cache_approach_result = basic_approximation.basic_approximation(U, BasisSet.CLIFFORD_T, 0.001, 5) - traversal_approach_result = basic_approximation_traversal.basic_approximation(U, BasisSet.CLIFFORD_T, 0.001, 5) - - print("-------------------------------------------") - - print(f"U: {U_name}") - - print("**************** Result ****************") - - print(f"Cache approach: {cache_approach_result['name']}") - print(f"Traversal approach: {traversal_approach_result.name}") - - print("**************** Time Taken **************** ") - print(f"Cache approach: {cache_approach_execution_time}") - print(f"Traversal approach: {traversal_approach_execution_time}") - - print("-------------------------------------------") - -if __name__ == '__main__': - test_time(U1, ['t', 'h', 's', 'h', 't']) - test_time(U2, ['s', 'h', 's']) - test_time(U3, ['h', 't', 'h', 's']) - test_time(U4, ['s', 's', 'h', 't']) \ No newline at end of file diff --git a/src/pyqasm/algorithms/solovay_kitaev/generator.py b/src/pyqasm/algorithms/solovay_kitaev/generator.py deleted file mode 100644 index edbe80ed..00000000 --- a/src/pyqasm/algorithms/solovay_kitaev/generator.py +++ /dev/null @@ -1,102 +0,0 @@ -import pickle -import sys -from collections import deque - -import numpy as np - -gate_sets = { - "clifford_T": [ - { - "name": "h", - "identity": { - "group": "h", - "weight": 0.5 - }, - "matrix": (1 / np.sqrt(2)) * np.array([[1, 1], [1, -1]]) - }, - { - "name": "s", - "identity": { - "group": "s-t", - "weight": 0.25 - }, - "matrix": np.array([[1, 0], [0, 1j]]) - }, - { - "name": "t", - "identity": { - "group": "s-t", - "weight": 0.125 - }, - "matrix": np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]), - }, - ] -} - - -def generate_solovay_kitaev_tree_cache(target_gate_set, max_depth, pkl_file_name): - queue = deque([{"name": [], "depth": 0, "matrix": np.eye(2), "identity": {"group": None, "weight": 0}}]) - result = [] - - while queue: - node = queue.popleft() - if node["depth"] == max_depth: - break - - for gate in target_gate_set: - new_group = gate["identity"]["group"] - new_weight = gate["identity"]["weight"] - current_group = node["identity"]["group"] - current_weight = node["identity"]["weight"] - - if current_group != new_group: - new_node = { - "name": node["name"] + [gate["name"]], - "depth": node["depth"] + 1, - "matrix": np.dot(node["matrix"], gate["matrix"]), - "identity": {"group": new_group, "weight": new_weight} - } - queue.append(new_node) - result.append(new_node) - elif current_weight + new_weight < 1: - new_node = { - "name": node["name"] + [gate["name"]], - "depth": node["depth"] + 1, - "matrix": np.dot(node["matrix"], gate["matrix"]), - "identity": {"group": current_group, "weight": current_weight + new_weight} - } - queue.append(new_node) - result.append(new_node) - - print(result) - - with open("cache/" + pkl_file_name, "wb") as f: - pickle.dump(result, f) - - -if __name__ == "__main__": - """ - How to use: - - Run this file direct, and pass the following command line arguments: - - target_gate_set: The target basis set of which you want to generate cached tree. - max_depth: Max depth of the tree which you want to cache. - pkl_file_name: Name of the pickel file in with you want to save the generated cache tree. - - Your command will look like this: - python generator.py - eg.: - python generator.py clifford_T 10 clifford-t_depth-10.pkl - - The file will be saved in cache dir. - """ - - - target_gate_set = sys.argv[1] - max_depth = sys.argv[2] - pkl_file_name = sys.argv[3] - - t = generate_solovay_kitaev_tree_cache( - gate_sets[target_gate_set], int(max_depth), pkl_file_name - ) diff --git a/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py b/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py index 97bc4d7d..a7797815 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py +++ b/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py @@ -1,71 +1,30 @@ import numpy as np from typing import List, Tuple -from pyqasm.algorithms.solovay_kitaev.generator import gate_sets from pyqasm.algorithms.solovay_kitaev.optimizer import optimize_gate_sequence -from pyqasm.maps.gates import BASIS_GATE_MAP, SELF_INVERTING_ONE_QUBIT_OP_SET, ST_GATE_INV_MAP, TWO_QUBIT_OP_SET from pyqasm.algorithms.solovay_kitaev.basic_approximation import basic_approximation +from pyqasm.algorithms.solovay_kitaev.utils import SU2Matrix, get_SU2Matrix_for_solovay_kitaev_algorithm from pyqasm.elements import BasisSet -gate_matrix = { - "h": (1 / np.sqrt(2)) * np.array([[1, 1], [1, -1]]), - "s": np.array([[1, 0], [0, 1j]]), - "t": np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]), - "sdg": np.array([[1, 0], [0, 1j]]).conj().T, - "tdg": np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]).conj().T, -} - -class SU2Matrix: - """Class representing a 2x2 Special Unitary matrix.""" - def __init__(self, matrix: np.ndarray, name: List[str]): - self.matrix = matrix - self.name = name - - def __mul__(self, other: 'SU2Matrix') -> 'SU2Matrix': - matrix = np.dot(self.matrix, other.matrix) - name = self.name.copy() - name.extend(other.name) - return SU2Matrix(matrix, name) - - def dagger(self) -> 'SU2Matrix': - """Returns the conjugate transpose.""" - matrix = self.matrix.conj().T - name = [] - for n in self.name[::-1]: - name.append(self._get_dagger_gate_name(n)) - - return SU2Matrix(matrix, name) - - def distance(self, other: 'SU2Matrix') -> float: - """Calculates the operator norm distance between two matrices.""" - diff = self.matrix - other.matrix - return np.linalg.norm(diff) - - def _get_dagger_gate_name(self,name: str): - if name in SELF_INVERTING_ONE_QUBIT_OP_SET: - return name - else: - return ST_GATE_INV_MAP[name] - - def __str__(self): - return f"name: {self.name}, matrix: {self.matrix}" def group_commutator(a: SU2Matrix, b: SU2Matrix) -> SU2Matrix: """Compute the group commutator [a,b] = aba^{-1}b^{-1}.""" return a * b * a.dagger() * b.dagger() -def find_basic_approximation(U: SU2Matrix, target_basis_set, accuracy=0.001, max_tree_depth=10) -> SU2Matrix: +def find_basic_approximation(U: SU2Matrix, target_basis_set, use_optimization, accuracy=1e-6, max_tree_depth=10) -> SU2Matrix: gates = basic_approximation(U, target_basis_set, accuracy, max_tree_depth) - return SU2Matrix(gates["matrix"], gates["name"]) + if use_optimization: + gates.name = optimize_gate_sequence(gates.name, target_basis_set) + return SU2Matrix(gates.matrix, gates.name) -def decompose_group_element(target: SU2Matrix, target_gate_set, basic_gates: List[SU2Matrix], depth: int) -> Tuple[List[SU2Matrix], float]: +def decompose_group_element(target: SU2Matrix, target_gate_set, basic_gates: List[SU2Matrix], depth: int, use_optimization) -> Tuple[List[SU2Matrix], float]: if depth == 0: - best_approx = find_basic_approximation(target.matrix, target_gate_set) + best_approx = find_basic_approximation(target.matrix, target_gate_set, use_optimization=use_optimization) return best_approx, target.distance(best_approx) # Recursive approximation - prev_sequence, prev_error = decompose_group_element(target, target_gate_set, basic_gates, depth - 1) + prev_sequence, prev_error = decompose_group_element(target, target_gate_set, basic_gates, depth - 1, use_optimization) # If previous approximation is good enough, return it # ERROR IS HARD CODED RIGHT NOW -> CHANGE THIS TO FIT USER-INPUT @@ -92,8 +51,8 @@ def decompose_group_element(target: SU2Matrix, target_gate_set, basic_gates: Lis # Add correction terms if best_v is not None and best_w is not None: - v_sequence, error = decompose_group_element(best_v, target_gate_set, basic_gates, depth - 1) - w_sequence, error = decompose_group_element(best_w, target_gate_set, basic_gates, depth - 1) + v_sequence, error = decompose_group_element(best_v, target_gate_set, basic_gates, depth - 1, use_optimization) + w_sequence, error = decompose_group_element(best_w, target_gate_set, basic_gates, depth - 1, use_optimization) result = group_commutator(v_sequence, w_sequence) * prev_sequence @@ -101,7 +60,7 @@ def decompose_group_element(target: SU2Matrix, target_gate_set, basic_gates: Lis return result, final_error -def solovay_kitaev(target: np.ndarray, target_basis_set, depth: int = 3) -> List[np.ndarray]: +def solovay_kitaev(target: np.ndarray, target_basis_set, depth: int = 3, use_optimization=True) -> List[np.ndarray]: """ Main function to run the Solovay-Kitaev algorithm. @@ -116,15 +75,16 @@ def solovay_kitaev(target: np.ndarray, target_basis_set, depth: int = 3) -> List # Convert inputs to SU2Matrix objects target_su2 = SU2Matrix(target, []) - target_basis_gate_list = BASIS_GATE_MAP[target_basis_set] - basic_gates_su2 = [SU2Matrix(gate_matrix[gate], [gate]) for gate in target_basis_gate_list if gate not in TWO_QUBIT_OP_SET] - + basic_gates_su2 = get_SU2Matrix_for_solovay_kitaev_algorithm(target_basis_set) # Run the decomposition - sequence, error = decompose_group_element(target_su2, target_basis_set, basic_gates_su2, depth) + sequence, _ = decompose_group_element(target_su2, target_basis_set, basic_gates_su2, depth, use_optimization) + if use_optimization: + sequence.name = optimize_gate_sequence(sequence.name, target_basis_set) + return sequence + return sequence - # return optimize_gate_sequence(sequence, target_basis_set) if __name__ == '__main__': U = np.array([[0.70711, 0.70711j], @@ -141,7 +101,4 @@ def solovay_kitaev(target: np.ndarray, target_basis_set, depth: int = 3) -> List print(np.allclose(r0.matrix, r1.matrix)) # Output: True print(np.allclose(r1.matrix, r2.matrix)) # Output: True - print(np.allclose(r2.matrix, r0.matrix)) # Output: True - - # Test optimizer - print(optimize_gate_sequence(r2.name, BasisSet.CLIFFORD_T)) \ No newline at end of file + print(np.allclose(r2.matrix, r0.matrix)) # Output: True \ No newline at end of file diff --git a/src/pyqasm/algorithms/solovay_kitaev/utils.py b/src/pyqasm/algorithms/solovay_kitaev/utils.py new file mode 100644 index 00000000..1a0259fe --- /dev/null +++ b/src/pyqasm/algorithms/solovay_kitaev/utils.py @@ -0,0 +1,103 @@ +import numpy as np +from typing import List + +from pyqasm.elements import BasisSet +from pyqasm.maps.gates import GATE_ENTITY_DATA, SELF_INVERTING_ONE_QUBIT_OP_SET, ST_GATE_INV_MAP + +class SU2Matrix: + """Class representing a 2x2 Special Unitary matrix.""" + def __init__(self, matrix: np.ndarray, name: List[str]): + self.matrix = matrix + self.name = name + + def __mul__(self, other: 'SU2Matrix') -> 'SU2Matrix': + matrix = np.dot(self.matrix, other.matrix) + name = self.name.copy() + name.extend(other.name) + return SU2Matrix(matrix, name) + + def dagger(self) -> 'SU2Matrix': + """Returns the conjugate transpose.""" + matrix = self.matrix.conj().T + name = [] + for n in self.name[::-1]: + name.append(self._get_dagger_gate_name(n)) + + return SU2Matrix(matrix, name) + + def distance(self, other: 'SU2Matrix') -> float: + """Calculates the operator norm distance between two matrices.""" + diff = self.matrix - other.matrix + return np.linalg.norm(diff) + + def _get_dagger_gate_name(self,name: str): + if name in SELF_INVERTING_ONE_QUBIT_OP_SET: + return name + else: + return ST_GATE_INV_MAP[name] + + def __str__(self): + return f"name: {self.name}, matrix: {self.matrix}" + +class TU2Matrix: + def __init__(self, matrix: np.ndarray, name: List[str], identity_group:str, identity_weight:float): + self.matrix = matrix + self.name = name + self.identity_group = identity_group + self.identity_weight = identity_weight + + def __mul__(self, other: 'TU2Matrix') -> 'TU2Matrix': + matrix = np.dot(self.matrix, other.matrix) + name = self.name.copy() + name.extend(other.name) + identity_weight = 0 + identity_group = '' + if self.identity_group == other.identity_group: + identity_weight = self.identity_weight + other.identity_weight + else: + identity_group = other.identity_group + identity_weight = other.identity_weight + + return TU2Matrix(matrix, name, identity_group, identity_weight) + + def can_multiple(self, other: 'TU2Matrix'): + if self.identity_group != other.identity_group: + return True + + if self.identity_weight + other.identity_weight < 1: + return True + + return False + + def get_diff(self, U): + # trace_diff = np.abs(np.trace(np.dot(self.matrix.conj().T, U) - np.identity(2))) + diff = np.linalg.norm(self.matrix - U, 2) + return diff + + def __str__(self): + # return f"name: {self.name}" + return f"name: {self.name}, matrix: {self.matrix}" + + def copy(self) -> 'TU2Matrix': + return TU2Matrix(self.matrix.copy(), self.name.copy(), self.identity_group, self.identity_weight) + +def get_SU2Matrix_for_solovay_kitaev_algorithm(target_basis_set)->List[SU2Matrix]: + gate_list = GATE_ENTITY_DATA[target_basis_set] + return [ SU2Matrix(gate["matrix"], [gate["name"]]) for gate in gate_list ] + +def get_TU2Matrix_for_basic_approximation(target_basis_set)->List[TU2Matrix]: + whole_gate_list = GATE_ENTITY_DATA[target_basis_set] + required_gate_list = [gate for gate in whole_gate_list if gate["used_for_basic_approximation"]] + + return [TU2Matrix( + gate["matrix"], + [gate["name"]], + gate["identity"]["group"], + gate["identity"]["weight"] + ) for gate in required_gate_list] + + +if __name__ == '__main__': + result = get_SU2Matrix_for_solovay_kitaev_algorithm(BasisSet.CLIFFORD_T) + for i in result: + print(i) \ No newline at end of file diff --git a/src/pyqasm/maps/gates.py b/src/pyqasm/maps/gates.py index b6f2d262..ebc05f98 100644 --- a/src/pyqasm/maps/gates.py +++ b/src/pyqasm/maps/gates.py @@ -1111,6 +1111,55 @@ def two_qubit_gate_op( BasisSet.CLIFFORD_T: {"h", "t", "s", "cx", "tdg", "sdg"}, } +GATE_ENTITY_DATA = { + BasisSet.CLIFFORD_T: [ + { + "name": "h", + "identity": { + "group": "h", + "weight": 0.5 + }, + "matrix": (1 / np.sqrt(2)) * np.array([[1, 1], [1, -1]]), + "used_for_basic_approximation": True, + }, + { + "name": "s", + "identity": { + "group": "s-t", + "weight": 0.25 + }, + "matrix": np.array([[1, 0], [0, 1j]]), + "used_for_basic_approximation": True, + }, + { + "name": "t", + "identity": { + "group": "s-t", + "weight": 0.125 + }, + "matrix": np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]), + "used_for_basic_approximation": True, + }, + { + "name": "sdg", + "identity": { + "group": "sdg-tdg", + "weight": 0.25 + }, + "matrix": np.array([[1, 0], [0, 1j]]).conj().T, + "used_for_basic_approximation": False, + }, + { + "name": "tdg", + "identity": { + "group": "sdg-tdg", + "weight": 0.125 + }, + "matrix": np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]).conj().T, + "used_for_basic_approximation": False, + }, + ] +} def map_qasm_op_to_callable(op_name: str) -> tuple[Callable, int]: """ @@ -1158,7 +1207,6 @@ def map_qasm_op_to_callable(op_name: str) -> tuple[Callable, int]: "U2": u2_inv_gate, "u2": u2_inv_gate, } -TWO_QUBIT_OP_SET = {"cx", "cz", "swap"} def map_qasm_inv_op_to_callable(op_name: str): From 89ec436f93e4d422b5e450c5472fa9587f2bf1ed Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Sun, 9 Mar 2025 18:49:56 +0530 Subject: [PATCH 10/20] Formatted using isort and black --- .../solovay_kitaev/basic_approximation.py | 46 ++++---- .../algorithms/solovay_kitaev/optimizer.py | 82 ++++++++------ .../solovay_kitaev/solovay_kitaev.py | 105 +++++++++++------- src/pyqasm/algorithms/solovay_kitaev/utils.py | 84 ++++++++------ src/pyqasm/decomposer.py | 18 +-- src/pyqasm/maps/decomposition_rules.py | 32 +++--- src/pyqasm/maps/gates.py | 26 +---- 7 files changed, 215 insertions(+), 178 deletions(-) diff --git a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py index 49954613..d586b466 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py +++ b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py @@ -1,73 +1,77 @@ import numpy as np -from pyqasm.elements import BasisSet from pyqasm.algorithms.solovay_kitaev.utils import TU2Matrix, get_TU2Matrix_for_basic_approximation - +from pyqasm.elements import BasisSet # Constants best_gate = None closest_gate = None closest_diff = float("inf") -def rescursive_traversal(U, A, target_gate_set_list, current_depth, accuracy=0.001, max_tree_depth=3): + +def rescursive_traversal( + U, A, target_gate_set_list, current_depth, accuracy=0.001, max_tree_depth=3 +): if current_depth >= max_tree_depth: return - + global closest_diff, closest_gate, best_gate - + if best_gate: return - + for gate in target_gate_set_list: if not A.can_multiple(gate): continue A_copy = A.copy() - A = A*gate - + A = A * gate + diff = A.get_diff(U) if diff < accuracy: best_gate = A.copy() return best_gate - + # Update the closest gate if the current one is closer if diff < closest_diff: closest_diff = diff closest_gate = A.copy() - + # print(A.name) # if A.name == ['s', 'h', 's']: # print(A) # print(diff) - rescursive_traversal(U, A.copy(), target_gate_set_list, current_depth+1, accuracy, max_tree_depth) + rescursive_traversal( + U, A.copy(), target_gate_set_list, current_depth + 1, accuracy, max_tree_depth + ) A = A_copy.copy() - + pass + def basic_approximation(U, target_gate_set, accuracy=0.001, max_tree_depth=3): global closest_diff, closest_gate, best_gate - + A = TU2Matrix(np.identity(2), [], None, None) target_gate_set_list = get_TU2Matrix_for_basic_approximation(target_gate_set) current_depth = 0 - rescursive_traversal(U, A.copy(), target_gate_set_list,current_depth, accuracy, max_tree_depth) - + rescursive_traversal(U, A.copy(), target_gate_set_list, current_depth, accuracy, max_tree_depth) + result = None - + if best_gate: result = best_gate.copy() else: result = closest_gate.copy() - + # Reset global variables best_gate = None closest_gate = None closest_diff = float("inf") - + return result if __name__ == "__main__": - U = np.array([[0.70711, 0.70711j], - [0.70711j, 0.70711]]) - + U = np.array([[0.70711, 0.70711j], [0.70711j, 0.70711]]) + print(basic_approximation(U, BasisSet.CLIFFORD_T, 0.0001, 3)) diff --git a/src/pyqasm/algorithms/solovay_kitaev/optimizer.py b/src/pyqasm/algorithms/solovay_kitaev/optimizer.py index 4a2f2e94..a6416066 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/optimizer.py +++ b/src/pyqasm/algorithms/solovay_kitaev/optimizer.py @@ -1,31 +1,18 @@ import numpy as np + from pyqasm.elements import BasisSet IDENTITY_WEIGHT_GROUP = { BasisSet.CLIFFORD_T: { - "h": { - "group": "h", - "weight": 0.5 - }, - "s": { - "group": "s-t", - "weight": 0.25 - }, - "t": { - "group": "s-t", - "weight": 0.125 - }, - "sdg": { - "group": "sdg-tdg", - "weight": 0.25 - }, - "tdg": { - "group": "sdg-tdg", - "weight": 0.125 - }, + "h": {"group": "h", "weight": 0.5}, + "s": {"group": "s-t", "weight": 0.25}, + "t": {"group": "s-t", "weight": 0.125}, + "sdg": {"group": "sdg-tdg", "weight": 0.25}, + "tdg": {"group": "sdg-tdg", "weight": 0.125}, } } + def optimize_gate_sequence(seq: list[str], target_basis_set): target_identity_weight_group = IDENTITY_WEIGHT_GROUP[target_basis_set] for _ in range(int(1e6)): @@ -37,7 +24,7 @@ def optimize_gate_sequence(seq: list[str], target_basis_set): for i, gate_name in enumerate(seq): if gate_name not in target_identity_weight_group: continue - + gate = target_identity_weight_group[gate_name] new_group = gate["group"] new_weight = gate["weight"] @@ -50,30 +37,55 @@ def optimize_gate_sequence(seq: list[str], target_basis_set): current_weight += new_weight if current_weight == 1: - seq = seq[:start_index] + seq[i+1:] + seq = seq[:start_index] + seq[i + 1 :] changed = True break elif current_weight > 1: remaining_weight = current_weight - 1 for key, value in target_identity_weight_group.items(): if value["group"] == current_group and value["weight"] == remaining_weight: - seq = seq[:start_index] + [key] + seq[i+1:] + seq = seq[:start_index] + [key] + seq[i + 1 :] changed = True break break if not changed: - return seq + return seq + + +if __name__ == "__main__": + s1 = ["s", "s", "s", "t", "t", "tdg", "sdg", "sdg", "sdg", "tdg", "s", "h", "s"] + s2 = [ + "t", + "s", + "s", + "s", + "t", + "tdg", + "tdg", + "sdg", + "sdg", + "sdg", + "t", + "s", + "s", + "s", + "t", + "tdg", + "tdg", + "sdg", + "sdg", + "sdg", + "s", + "h", + "s", + ] + s3 = ["h", "s", "s", "t", "t", "s", "t"] # ['h', 't'] + s4 = ["h", "s", "s", "t", "t", "s", "h"] # [] + s5 = ["h", "s", "s", "t", "h", "h", "t", "s", "h", "t"] # ['t'] -if __name__ == '__main__': - s1 = ['s', 's', 's', 't', 't', 'tdg', 'sdg', 'sdg', 'sdg', 'tdg', 's', 'h', 's'] - s2 = ['t', 's', 's', 's', 't', 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', 't', 's', 's', 's', 't', 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', 's', 'h', 's'] - s3 = ['h', 's', 's', 't', 't', 's', 't'] # ['h', 't'] - s4 = ['h', 's', 's', 't', 't', 's', 'h'] # [] - s5 = ['h', 's', 's', 't', 'h', 'h', 't', 's', 'h', 't'] # ['t'] - - print(optimize_gate_sequence(s1, BasisSet.CLIFFORD_T) == ['s', 'h', 's']) - print(optimize_gate_sequence(s2, BasisSet.CLIFFORD_T) == ['s', 'h', 's']) - print(optimize_gate_sequence(s3, BasisSet.CLIFFORD_T) == ['h', 't']) + print(optimize_gate_sequence(s1, BasisSet.CLIFFORD_T) == ["s", "h", "s"]) + print(optimize_gate_sequence(s2, BasisSet.CLIFFORD_T) == ["s", "h", "s"]) + print(optimize_gate_sequence(s3, BasisSet.CLIFFORD_T) == ["h", "t"]) print(optimize_gate_sequence(s4, BasisSet.CLIFFORD_T) == []) - print(optimize_gate_sequence(s5, BasisSet.CLIFFORD_T) == ['t']) \ No newline at end of file + print(optimize_gate_sequence(s5, BasisSet.CLIFFORD_T) == ["t"]) diff --git a/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py b/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py index a7797815..38e4c123 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py +++ b/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py @@ -1,42 +1,56 @@ -import numpy as np from typing import List, Tuple -from pyqasm.algorithms.solovay_kitaev.optimizer import optimize_gate_sequence +import numpy as np + from pyqasm.algorithms.solovay_kitaev.basic_approximation import basic_approximation -from pyqasm.algorithms.solovay_kitaev.utils import SU2Matrix, get_SU2Matrix_for_solovay_kitaev_algorithm +from pyqasm.algorithms.solovay_kitaev.optimizer import optimize_gate_sequence +from pyqasm.algorithms.solovay_kitaev.utils import ( + SU2Matrix, + get_SU2Matrix_for_solovay_kitaev_algorithm, +) from pyqasm.elements import BasisSet def group_commutator(a: SU2Matrix, b: SU2Matrix) -> SU2Matrix: """Compute the group commutator [a,b] = aba^{-1}b^{-1}.""" - return a * b * a.dagger() * b.dagger() + return a * b * a.dagger() * b.dagger() + -def find_basic_approximation(U: SU2Matrix, target_basis_set, use_optimization, accuracy=1e-6, max_tree_depth=10) -> SU2Matrix: +def find_basic_approximation( + U: SU2Matrix, target_basis_set, use_optimization, accuracy=1e-6, max_tree_depth=10 +) -> SU2Matrix: gates = basic_approximation(U, target_basis_set, accuracy, max_tree_depth) if use_optimization: gates.name = optimize_gate_sequence(gates.name, target_basis_set) return SU2Matrix(gates.matrix, gates.name) -def decompose_group_element(target: SU2Matrix, target_gate_set, basic_gates: List[SU2Matrix], depth: int, use_optimization) -> Tuple[List[SU2Matrix], float]: - + +def decompose_group_element( + target: SU2Matrix, target_gate_set, basic_gates: List[SU2Matrix], depth: int, use_optimization +) -> Tuple[List[SU2Matrix], float]: + if depth == 0: - best_approx = find_basic_approximation(target.matrix, target_gate_set, use_optimization=use_optimization) + best_approx = find_basic_approximation( + target.matrix, target_gate_set, use_optimization=use_optimization + ) return best_approx, target.distance(best_approx) - + # Recursive approximation - prev_sequence, prev_error = decompose_group_element(target, target_gate_set, basic_gates, depth - 1, use_optimization) - + prev_sequence, prev_error = decompose_group_element( + target, target_gate_set, basic_gates, depth - 1, use_optimization + ) + # If previous approximation is good enough, return it # ERROR IS HARD CODED RIGHT NOW -> CHANGE THIS TO FIT USER-INPUT if prev_error < 1e-6: return prev_sequence, prev_error - + error = target * prev_sequence.dagger() - + # Find Va and Vb such that their group commutator approximates the error best_v = None best_w = None - best_error = float('inf') + best_error = float("inf") for v in basic_gates: for w in basic_gates: @@ -46,59 +60,72 @@ def decompose_group_element(target: SU2Matrix, target_gate_set, basic_gates: Lis best_error = curr_error best_v = v best_w = w - + result = prev_sequence - + # Add correction terms if best_v is not None and best_w is not None: - v_sequence, error = decompose_group_element(best_v, target_gate_set, basic_gates, depth - 1, use_optimization) - w_sequence, error = decompose_group_element(best_w, target_gate_set, basic_gates, depth - 1, use_optimization) + v_sequence, error = decompose_group_element( + best_v, target_gate_set, basic_gates, depth - 1, use_optimization + ) + w_sequence, error = decompose_group_element( + best_w, target_gate_set, basic_gates, depth - 1, use_optimization + ) result = group_commutator(v_sequence, w_sequence) * prev_sequence - + final_error = target.distance(result) - + return result, final_error -def solovay_kitaev(target: np.ndarray, target_basis_set, depth: int = 3, use_optimization=True) -> List[np.ndarray]: + +def solovay_kitaev( + target: np.ndarray, target_basis_set, depth: int = 3, use_optimization=True +) -> List[np.ndarray]: """ Main function to run the Solovay-Kitaev algorithm. - + Args: target: Target unitary matrix as numpy array target_basis_set: The target basis set to rebase the module to. depth: Recursion depth - + Returns: List of gates that approximate the target unitary """ # Convert inputs to SU2Matrix objects target_su2 = SU2Matrix(target, []) - + basic_gates_su2 = get_SU2Matrix_for_solovay_kitaev_algorithm(target_basis_set) # Run the decomposition - sequence, _ = decompose_group_element(target_su2, target_basis_set, basic_gates_su2, depth, use_optimization) + sequence, _ = decompose_group_element( + target_su2, target_basis_set, basic_gates_su2, depth, use_optimization + ) if use_optimization: sequence.name = optimize_gate_sequence(sequence.name, target_basis_set) return sequence - + return sequence - -if __name__ == '__main__': - U = np.array([[0.70711, 0.70711j], - [0.70711j, 0.70711]]) - + + +if __name__ == "__main__": + U = np.array([[0.70711, 0.70711j], [0.70711j, 0.70711]]) + r0 = solovay_kitaev(U, BasisSet.CLIFFORD_T, depth=0) print(r0.name) # Output: ['s', 'h', 's'] - + r1 = solovay_kitaev(U, BasisSet.CLIFFORD_T, depth=1) - print(r1.name) # Output: ['s', 's', 's', 't', 't', 'tdg', 'sdg', 'sdg', 'sdg', 'tdg', 's', 'h', 's'] - + print( + r1.name + ) # Output: ['s', 's', 's', 't', 't', 'tdg', 'sdg', 'sdg', 'sdg', 'tdg', 's', 'h', 's'] + r2 = solovay_kitaev(U, BasisSet.CLIFFORD_T, depth=2) - print(r2.name) # Output: ['t', 's', 's', 's', 't', 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', 't', 's', 's', 's', 't', 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', 's', 'h', 's'] - - print(np.allclose(r0.matrix, r1.matrix)) # Output: True - print(np.allclose(r1.matrix, r2.matrix)) # Output: True - print(np.allclose(r2.matrix, r0.matrix)) # Output: True \ No newline at end of file + print( + r2.name + ) # Output: ['t', 's', 's', 's', 't', 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', 't', 's', 's', 's', 't', 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', 's', 'h', 's'] + + print(np.allclose(r0.matrix, r1.matrix)) # Output: True + print(np.allclose(r1.matrix, r2.matrix)) # Output: True + print(np.allclose(r2.matrix, r0.matrix)) # Output: True diff --git a/src/pyqasm/algorithms/solovay_kitaev/utils.py b/src/pyqasm/algorithms/solovay_kitaev/utils.py index 1a0259fe..746d013b 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/utils.py +++ b/src/pyqasm/algorithms/solovay_kitaev/utils.py @@ -1,103 +1,113 @@ -import numpy as np from typing import List +import numpy as np + from pyqasm.elements import BasisSet from pyqasm.maps.gates import GATE_ENTITY_DATA, SELF_INVERTING_ONE_QUBIT_OP_SET, ST_GATE_INV_MAP + class SU2Matrix: """Class representing a 2x2 Special Unitary matrix.""" + def __init__(self, matrix: np.ndarray, name: List[str]): self.matrix = matrix self.name = name - def __mul__(self, other: 'SU2Matrix') -> 'SU2Matrix': + def __mul__(self, other: "SU2Matrix") -> "SU2Matrix": matrix = np.dot(self.matrix, other.matrix) name = self.name.copy() name.extend(other.name) return SU2Matrix(matrix, name) - def dagger(self) -> 'SU2Matrix': + def dagger(self) -> "SU2Matrix": """Returns the conjugate transpose.""" matrix = self.matrix.conj().T name = [] for n in self.name[::-1]: name.append(self._get_dagger_gate_name(n)) - + return SU2Matrix(matrix, name) - def distance(self, other: 'SU2Matrix') -> float: + def distance(self, other: "SU2Matrix") -> float: """Calculates the operator norm distance between two matrices.""" diff = self.matrix - other.matrix return np.linalg.norm(diff) - - def _get_dagger_gate_name(self,name: str): + + def _get_dagger_gate_name(self, name: str): if name in SELF_INVERTING_ONE_QUBIT_OP_SET: return name else: return ST_GATE_INV_MAP[name] - + def __str__(self): return f"name: {self.name}, matrix: {self.matrix}" - + + class TU2Matrix: - def __init__(self, matrix: np.ndarray, name: List[str], identity_group:str, identity_weight:float): + def __init__( + self, matrix: np.ndarray, name: List[str], identity_group: str, identity_weight: float + ): self.matrix = matrix self.name = name self.identity_group = identity_group self.identity_weight = identity_weight - def __mul__(self, other: 'TU2Matrix') -> 'TU2Matrix': + def __mul__(self, other: "TU2Matrix") -> "TU2Matrix": matrix = np.dot(self.matrix, other.matrix) name = self.name.copy() name.extend(other.name) identity_weight = 0 - identity_group = '' + identity_group = "" if self.identity_group == other.identity_group: identity_weight = self.identity_weight + other.identity_weight else: identity_group = other.identity_group identity_weight = other.identity_weight - + return TU2Matrix(matrix, name, identity_group, identity_weight) - - def can_multiple(self, other: 'TU2Matrix'): + + def can_multiple(self, other: "TU2Matrix"): if self.identity_group != other.identity_group: return True - + if self.identity_weight + other.identity_weight < 1: return True - + return False def get_diff(self, U): # trace_diff = np.abs(np.trace(np.dot(self.matrix.conj().T, U) - np.identity(2))) diff = np.linalg.norm(self.matrix - U, 2) return diff - + def __str__(self): # return f"name: {self.name}" return f"name: {self.name}, matrix: {self.matrix}" - - def copy(self) -> 'TU2Matrix': - return TU2Matrix(self.matrix.copy(), self.name.copy(), self.identity_group, self.identity_weight) - -def get_SU2Matrix_for_solovay_kitaev_algorithm(target_basis_set)->List[SU2Matrix]: + + def copy(self) -> "TU2Matrix": + return TU2Matrix( + self.matrix.copy(), self.name.copy(), self.identity_group, self.identity_weight + ) + + +def get_SU2Matrix_for_solovay_kitaev_algorithm(target_basis_set) -> List[SU2Matrix]: gate_list = GATE_ENTITY_DATA[target_basis_set] - return [ SU2Matrix(gate["matrix"], [gate["name"]]) for gate in gate_list ] - -def get_TU2Matrix_for_basic_approximation(target_basis_set)->List[TU2Matrix]: + return [SU2Matrix(gate["matrix"], [gate["name"]]) for gate in gate_list] + + +def get_TU2Matrix_for_basic_approximation(target_basis_set) -> List[TU2Matrix]: whole_gate_list = GATE_ENTITY_DATA[target_basis_set] required_gate_list = [gate for gate in whole_gate_list if gate["used_for_basic_approximation"]] - - return [TU2Matrix( - gate["matrix"], - [gate["name"]], - gate["identity"]["group"], - gate["identity"]["weight"] - ) for gate in required_gate_list] - - -if __name__ == '__main__': + + return [ + TU2Matrix( + gate["matrix"], [gate["name"]], gate["identity"]["group"], gate["identity"]["weight"] + ) + for gate in required_gate_list + ] + + +if __name__ == "__main__": result = get_SU2Matrix_for_solovay_kitaev_algorithm(BasisSet.CLIFFORD_T) for i in result: - print(i) \ No newline at end of file + print(i) diff --git a/src/pyqasm/decomposer.py b/src/pyqasm/decomposer.py index a569d94d..e30f4b4d 100644 --- a/src/pyqasm/decomposer.py +++ b/src/pyqasm/decomposer.py @@ -16,7 +16,11 @@ from openqasm3.ast import BranchingStatement, QuantumGate from pyqasm.exceptions import RebaseError -from pyqasm.maps.decomposition_rules import DECOMPOSITION_RULES, AppliedQubit, ROTATIONAL_LOOKUP_RULES +from pyqasm.maps.decomposition_rules import ( + DECOMPOSITION_RULES, + ROTATIONAL_LOOKUP_RULES, + AppliedQubit, +) from pyqasm.maps.expressions import CONSTANTS_MAP from pyqasm.maps.gates import BASIS_GATE_MAP @@ -49,19 +53,15 @@ def process_gate_statement(cls, gate_name, statement, target_basis_set): elif gate_name in decomposition_rules: # Decompose the gates rule_list = decomposition_rules[gate_name] - processed_gates_list = cls._get_decomposed_gates( - rule_list, statement - ) + processed_gates_list = cls._get_decomposed_gates(rule_list, statement) elif gate_name in {"rx", "ry", "rz"}: # Use lookup table if ∅ is pi, pi/2 or pi/4 theta = statement.arguments[0].value - if theta in [CONSTANTS_MAP["pi"], CONSTANTS_MAP["pi"]/2, CONSTANTS_MAP["pi"]/4]: + if theta in [CONSTANTS_MAP["pi"], CONSTANTS_MAP["pi"] / 2, CONSTANTS_MAP["pi"] / 4]: rotational_lookup_rules = ROTATIONAL_LOOKUP_RULES[target_basis_set] rule_list = rotational_lookup_rules[gate_name][theta] - processed_gates_list = cls._get_decomposed_gates( - rule_list, statement - ) - + processed_gates_list = cls._get_decomposed_gates(rule_list, statement) + # Approximate parameterized gates using Solovay-Kitaev # Example - # approx_gates = solovay_kitaev_algo( diff --git a/src/pyqasm/maps/decomposition_rules.py b/src/pyqasm/maps/decomposition_rules.py index 92fac8ec..4b005ab9 100644 --- a/src/pyqasm/maps/decomposition_rules.py +++ b/src/pyqasm/maps/decomposition_rules.py @@ -99,41 +99,37 @@ class AppliedQubit(Enum): ROTATIONAL_LOOKUP_RULES = { BasisSet.CLIFFORD_T: { "rz": { - CONSTANTS_MAP['pi']: [ + CONSTANTS_MAP["pi"]: [ {"gate": "s"}, {"gate": "s"}, ], - CONSTANTS_MAP['pi']/2 :[ - {"gate": "s"} - ], - CONSTANTS_MAP['pi']/4 :[ - {"gate": "t"} - ] + CONSTANTS_MAP["pi"] / 2: [{"gate": "s"}], + CONSTANTS_MAP["pi"] / 4: [{"gate": "t"}], }, - # Rx(∅) = H.Rz(∅).H "rx": { - CONSTANTS_MAP['pi']: [ + CONSTANTS_MAP["pi"]: [ {"gate": "h"}, {"gate": "s"}, {"gate": "s"}, {"gate": "h"}, ], - CONSTANTS_MAP['pi']/2 :[ + CONSTANTS_MAP["pi"] + / 2: [ {"gate": "h"}, {"gate": "s"}, {"gate": "h"}, ], - CONSTANTS_MAP['pi']/4 :[ + CONSTANTS_MAP["pi"] + / 4: [ {"gate": "h"}, {"gate": "t"}, {"gate": "h"}, - ] + ], }, - # Ry(∅) = S†.H.Rz(∅).H.S "ry": { - CONSTANTS_MAP['pi']: [ + CONSTANTS_MAP["pi"]: [ {"gate": "sdg"}, {"gate": "h"}, {"gate": "s"}, @@ -141,20 +137,22 @@ class AppliedQubit(Enum): {"gate": "h"}, {"gate": "s"}, ], - CONSTANTS_MAP['pi']/2 :[ + CONSTANTS_MAP["pi"] + / 2: [ {"gate": "sdg"}, {"gate": "h"}, {"gate": "s"}, {"gate": "h"}, {"gate": "s"}, ], - CONSTANTS_MAP['pi']/4 :[ + CONSTANTS_MAP["pi"] + / 4: [ {"gate": "sdg"}, {"gate": "h"}, {"gate": "t"}, {"gate": "h"}, {"gate": "s"}, - ] + ], }, } } diff --git a/src/pyqasm/maps/gates.py b/src/pyqasm/maps/gates.py index ebc05f98..e038c40b 100644 --- a/src/pyqasm/maps/gates.py +++ b/src/pyqasm/maps/gates.py @@ -1115,52 +1115,38 @@ def two_qubit_gate_op( BasisSet.CLIFFORD_T: [ { "name": "h", - "identity": { - "group": "h", - "weight": 0.5 - }, + "identity": {"group": "h", "weight": 0.5}, "matrix": (1 / np.sqrt(2)) * np.array([[1, 1], [1, -1]]), "used_for_basic_approximation": True, }, { "name": "s", - "identity": { - "group": "s-t", - "weight": 0.25 - }, + "identity": {"group": "s-t", "weight": 0.25}, "matrix": np.array([[1, 0], [0, 1j]]), "used_for_basic_approximation": True, }, { "name": "t", - "identity": { - "group": "s-t", - "weight": 0.125 - }, + "identity": {"group": "s-t", "weight": 0.125}, "matrix": np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]), "used_for_basic_approximation": True, }, { "name": "sdg", - "identity": { - "group": "sdg-tdg", - "weight": 0.25 - }, + "identity": {"group": "sdg-tdg", "weight": 0.25}, "matrix": np.array([[1, 0], [0, 1j]]).conj().T, "used_for_basic_approximation": False, }, { "name": "tdg", - "identity": { - "group": "sdg-tdg", - "weight": 0.125 - }, + "identity": {"group": "sdg-tdg", "weight": 0.125}, "matrix": np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]).conj().T, "used_for_basic_approximation": False, }, ] } + def map_qasm_op_to_callable(op_name: str) -> tuple[Callable, int]: """ Map a QASM operation to a callable. From 9b28f66338d7743ceda1c6975b2d15659e9bd474 Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Mon, 10 Mar 2025 23:12:30 +0530 Subject: [PATCH 11/20] Formatted code, added proper docstrings and combined the code with rebase function. --- .../solovay_kitaev/basic_approximation.py | 103 ++++++++++------- .../algorithms/solovay_kitaev/optimizer.py | 29 ++--- .../solovay_kitaev/solovay_kitaev.py | 82 ++++++++++---- src/pyqasm/algorithms/solovay_kitaev/utils.py | 87 ++++++++++++--- src/pyqasm/decomposer.py | 104 +++++++++++++++--- src/pyqasm/modules/base.py | 10 +- 6 files changed, 304 insertions(+), 111 deletions(-) diff --git a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py index d586b466..44e2587d 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py +++ b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py @@ -1,60 +1,90 @@ +""" +Definition of the basic approximation algorithm for the Solovay-Kitaev theorem. +""" + import numpy as np -from pyqasm.algorithms.solovay_kitaev.utils import TU2Matrix, get_TU2Matrix_for_basic_approximation +from pyqasm.algorithms.solovay_kitaev.utils import TU2Matrix, get_tu2matrix_for_basic_approximation from pyqasm.elements import BasisSet -# Constants -best_gate = None -closest_gate = None -closest_diff = float("inf") - def rescursive_traversal( - U, A, target_gate_set_list, current_depth, accuracy=0.001, max_tree_depth=3 + target_matrix, approximated_matrix, target_gate_set_list, current_depth, params ): - if current_depth >= max_tree_depth: - return + """Recursively traverse the tree to find the best approximation of the target matrix. + Args: + target_matrix (np.ndarray): The target matrix to approximate. + approximated_matrix (TU2Matrix): The approximated matrix. + target_gate_set_list (list): The list of target gates to approximate. + current_depth (int): The current depth of the tree. + params (tuple): The parameters for the approximation. + + Returns: + float: The closest difference between the target and approximated matrix. + TU2Matrix: The closest approximated matrix. + TU2Matrix: The best approx + + """ + accuracy, max_tree_depth, closest_diff, closest_gate, best_gate = params - global closest_diff, closest_gate, best_gate + if current_depth >= max_tree_depth: + return closest_diff, closest_gate, best_gate if best_gate: - return + return closest_diff, closest_gate, best_gate for gate in target_gate_set_list: - if not A.can_multiple(gate): + if not approximated_matrix.can_multiple(gate): continue - A_copy = A.copy() - A = A * gate + approximated_matrix_copy = approximated_matrix.copy() + approximated_matrix = approximated_matrix * gate - diff = A.get_diff(U) + diff = approximated_matrix.distance(target_matrix) if diff < accuracy: - best_gate = A.copy() - return best_gate + best_gate = approximated_matrix.copy() + return closest_diff, closest_gate, best_gate # Update the closest gate if the current one is closer if diff < closest_diff: closest_diff = diff - closest_gate = A.copy() - - # print(A.name) - # if A.name == ['s', 'h', 's']: - # print(A) - # print(diff) - rescursive_traversal( - U, A.copy(), target_gate_set_list, current_depth + 1, accuracy, max_tree_depth + closest_gate = approximated_matrix.copy() + + closest_diff, closest_gate, best_gate = rescursive_traversal( + target_matrix, + approximated_matrix.copy(), + target_gate_set_list, + current_depth + 1, + (accuracy, max_tree_depth, closest_diff, closest_gate, best_gate), ) - A = A_copy.copy() + approximated_matrix = approximated_matrix_copy.copy() - pass + return closest_diff, closest_gate, best_gate -def basic_approximation(U, target_gate_set, accuracy=0.001, max_tree_depth=3): - global closest_diff, closest_gate, best_gate +def basic_approximation(target_matrix, target_gate_set, accuracy=0.001, max_tree_depth=3): + """Approximate the target matrix using the basic approximation algorithm. - A = TU2Matrix(np.identity(2), [], None, None) - target_gate_set_list = get_TU2Matrix_for_basic_approximation(target_gate_set) + Args: + target_matrix (np.ndarray): The target matrix to approximate. + target_gate_set (BasisSet): The target gate set to approximate. + accuracy (float): The accuracy of the approximation. + max_tree_depth (int): The maximum depth of the tree. + + Returns: + TU2Matrix: The approximated matrix. + """ + approximated_matrix = TU2Matrix(np.identity(2), [], None, None) + target_gate_set_list = get_tu2matrix_for_basic_approximation(target_gate_set) current_depth = 0 - rescursive_traversal(U, A.copy(), target_gate_set_list, current_depth, accuracy, max_tree_depth) + closest_diff = float("inf") + closest_gate = None + best_gate = None + + params = (accuracy, max_tree_depth, closest_diff, closest_gate, best_gate) + + closest_diff, closest_gate, best_gate = rescursive_traversal( + target_matrix, approximated_matrix.copy(), target_gate_set_list, current_depth, params + ) result = None @@ -63,15 +93,10 @@ def basic_approximation(U, target_gate_set, accuracy=0.001, max_tree_depth=3): else: result = closest_gate.copy() - # Reset global variables - best_gate = None - closest_gate = None - closest_diff = float("inf") - return result if __name__ == "__main__": - U = np.array([[0.70711, 0.70711j], [0.70711j, 0.70711]]) + target_matrix_U = np.array([[0.70711, 0.70711j], [0.70711j, 0.70711]]) - print(basic_approximation(U, BasisSet.CLIFFORD_T, 0.0001, 3)) + print(basic_approximation(target_matrix_U, BasisSet.CLIFFORD_T, 0.0001, 3)) diff --git a/src/pyqasm/algorithms/solovay_kitaev/optimizer.py b/src/pyqasm/algorithms/solovay_kitaev/optimizer.py index a6416066..55ab294a 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/optimizer.py +++ b/src/pyqasm/algorithms/solovay_kitaev/optimizer.py @@ -1,20 +1,21 @@ -import numpy as np +""" +Definition of the optimizer for the Solovay-Kitaev theorem. +""" +from pyqasm.algorithms.solovay_kitaev.utils import get_identity_weight_group_for_optimizer from pyqasm.elements import BasisSet -IDENTITY_WEIGHT_GROUP = { - BasisSet.CLIFFORD_T: { - "h": {"group": "h", "weight": 0.5}, - "s": {"group": "s-t", "weight": 0.25}, - "t": {"group": "s-t", "weight": 0.125}, - "sdg": {"group": "sdg-tdg", "weight": 0.25}, - "tdg": {"group": "sdg-tdg", "weight": 0.125}, - } -} - def optimize_gate_sequence(seq: list[str], target_basis_set): - target_identity_weight_group = IDENTITY_WEIGHT_GROUP[target_basis_set] + """Optimize a gate sequence based on the identity weight group. + Args: + seq (list[str]): The gate sequence to optimize. + target_basis_set (BasisSet): The target basis set. + + Returns: + list[str]: The optimized gate sequence. + """ + target_identity_weight_group = get_identity_weight_group_for_optimizer(target_basis_set) for _ in range(int(1e6)): current_group = None current_weight = 0 @@ -40,7 +41,7 @@ def optimize_gate_sequence(seq: list[str], target_basis_set): seq = seq[:start_index] + seq[i + 1 :] changed = True break - elif current_weight > 1: + if current_weight > 1: remaining_weight = current_weight - 1 for key, value in target_identity_weight_group.items(): if value["group"] == current_group and value["weight"] == remaining_weight: @@ -52,6 +53,8 @@ def optimize_gate_sequence(seq: list[str], target_basis_set): if not changed: return seq + return seq + if __name__ == "__main__": s1 = ["s", "s", "s", "t", "t", "tdg", "sdg", "sdg", "sdg", "tdg", "s", "h", "s"] diff --git a/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py b/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py index 38e4c123..1d97139d 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py +++ b/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py @@ -1,3 +1,7 @@ +""" +Definition of the Solovay-Kitaev algorithm. +""" + from typing import List, Tuple import numpy as np @@ -6,7 +10,7 @@ from pyqasm.algorithms.solovay_kitaev.optimizer import optimize_gate_sequence from pyqasm.algorithms.solovay_kitaev.utils import ( SU2Matrix, - get_SU2Matrix_for_solovay_kitaev_algorithm, + get_su2matrix_for_solovay_kitaev_algorithm, ) from pyqasm.elements import BasisSet @@ -17,17 +21,47 @@ def group_commutator(a: SU2Matrix, b: SU2Matrix) -> SU2Matrix: def find_basic_approximation( - U: SU2Matrix, target_basis_set, use_optimization, accuracy=1e-6, max_tree_depth=10 + target_matrix: SU2Matrix, target_basis_set, use_optimization, accuracy=1e-6, max_tree_depth=10 ) -> SU2Matrix: - gates = basic_approximation(U, target_basis_set, accuracy, max_tree_depth) + """Find the basic approximation of a target matrix. + + Args: + target_matrix (SU2Matrix): The target matrix to approximate. + target_basis_set (BasisSet): The basis set to use for the approximation. + use_optimization (bool): Whether to use optimization to reduce the number of gates. + accuracy (float): The accuracy of the approximation. + max_tree_depth (int): The maximum depth of the tree. + + Returns: + SU2Matrix: The basic approximation of the target matrix. + """ + gates = basic_approximation(target_matrix, target_basis_set, accuracy, max_tree_depth) if use_optimization: gates.name = optimize_gate_sequence(gates.name, target_basis_set) return SU2Matrix(gates.matrix, gates.name) def decompose_group_element( - target: SU2Matrix, target_gate_set, basic_gates: List[SU2Matrix], depth: int, use_optimization + target: SU2Matrix, + target_gate_set, + basic_gates: List[SU2Matrix], + depth, + accuracy, + use_optimization, ) -> Tuple[List[SU2Matrix], float]: + """Decompose a group element into a sequence of basic gates. + + Args: + target (SU2Matrix): The target group element. + target_gate_set (BasisSet): The target gate set. + basic_gates (List[SU2Matrix]): The basic gates to use for the approximation. + depth (int): The depth of the approximation. + accuracy (float): The accuracy of the approximation. + use_optimization (bool): Whether to use optimization to reduce the number of gates. + + Returns: + Tuple[List[SU2Matrix], float]: The sequence of basic gates and the error. + """ if depth == 0: best_approx = find_basic_approximation( @@ -37,12 +71,12 @@ def decompose_group_element( # Recursive approximation prev_sequence, prev_error = decompose_group_element( - target, target_gate_set, basic_gates, depth - 1, use_optimization + target, target_gate_set, basic_gates, depth - 1, accuracy, use_optimization ) # If previous approximation is good enough, return it # ERROR IS HARD CODED RIGHT NOW -> CHANGE THIS TO FIT USER-INPUT - if prev_error < 1e-6: + if prev_error < accuracy: return prev_sequence, prev_error error = target * prev_sequence.dagger() @@ -66,10 +100,10 @@ def decompose_group_element( # Add correction terms if best_v is not None and best_w is not None: v_sequence, error = decompose_group_element( - best_v, target_gate_set, basic_gates, depth - 1, use_optimization + best_v, target_gate_set, basic_gates, depth - 1, accuracy, use_optimization ) w_sequence, error = decompose_group_element( - best_w, target_gate_set, basic_gates, depth - 1, use_optimization + best_w, target_gate_set, basic_gates, depth - 1, accuracy, use_optimization ) result = group_commutator(v_sequence, w_sequence) * prev_sequence @@ -80,27 +114,29 @@ def decompose_group_element( def solovay_kitaev( - target: np.ndarray, target_basis_set, depth: int = 3, use_optimization=True + target: np.ndarray, target_basis_set, depth: int = 3, accuracy=1e-6, use_optimization=True ) -> List[np.ndarray]: """ Main function to run the Solovay-Kitaev algorithm. Args: - target: Target unitary matrix as numpy array - target_basis_set: The target basis set to rebase the module to. - depth: Recursion depth + target: The target unitary matrix to approximate + target_basis_set: The basis set to use for the approximation + depth: The depth of the approximation + accuracy: The accuracy of the approximation + use_optimization: Whether to use optimization to reduce the number Returns: - List of gates that approximate the target unitary + A list of gates that approximate the target unitary matrix """ # Convert inputs to SU2Matrix objects target_su2 = SU2Matrix(target, []) - basic_gates_su2 = get_SU2Matrix_for_solovay_kitaev_algorithm(target_basis_set) + basic_gates_su2 = get_su2matrix_for_solovay_kitaev_algorithm(target_basis_set) # Run the decomposition sequence, _ = decompose_group_element( - target_su2, target_basis_set, basic_gates_su2, depth, use_optimization + target_su2, target_basis_set, basic_gates_su2, depth, accuracy, use_optimization ) if use_optimization: @@ -111,20 +147,22 @@ def solovay_kitaev( if __name__ == "__main__": - U = np.array([[0.70711, 0.70711j], [0.70711j, 0.70711]]) + target_matrix_U = np.array([[0.70711, 0.70711j], [0.70711j, 0.70711]]) - r0 = solovay_kitaev(U, BasisSet.CLIFFORD_T, depth=0) + r0 = solovay_kitaev(target_matrix_U, BasisSet.CLIFFORD_T, depth=0) print(r0.name) # Output: ['s', 'h', 's'] - r1 = solovay_kitaev(U, BasisSet.CLIFFORD_T, depth=1) + r1 = solovay_kitaev(target_matrix_U, BasisSet.CLIFFORD_T, depth=1) print( r1.name ) # Output: ['s', 's', 's', 't', 't', 'tdg', 'sdg', 'sdg', 'sdg', 'tdg', 's', 'h', 's'] - r2 = solovay_kitaev(U, BasisSet.CLIFFORD_T, depth=2) - print( - r2.name - ) # Output: ['t', 's', 's', 's', 't', 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', 't', 's', 's', 's', 't', 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', 's', 'h', 's'] + r2 = solovay_kitaev(target_matrix_U, BasisSet.CLIFFORD_T, depth=2) + print(r2.name) # Output: ['t', 's', 's', 's', 't', + # 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', + # 't', 's', 's', 's', 't', + # 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', + # 's', 'h', 's'] print(np.allclose(r0.matrix, r1.matrix)) # Output: True print(np.allclose(r1.matrix, r2.matrix)) # Output: True diff --git a/src/pyqasm/algorithms/solovay_kitaev/utils.py b/src/pyqasm/algorithms/solovay_kitaev/utils.py index 746d013b..d55f8ee4 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/utils.py +++ b/src/pyqasm/algorithms/solovay_kitaev/utils.py @@ -1,3 +1,7 @@ +""" +Definition of common utility functions for the Solovay-Kitaev algorithm. +""" + from typing import List import numpy as np @@ -7,13 +11,24 @@ class SU2Matrix: - """Class representing a 2x2 Special Unitary matrix.""" + """Class representing a 2x2 Special Unitary matrix. + + Used for the Solovay-Kitaev algorithm. + """ def __init__(self, matrix: np.ndarray, name: List[str]): self.matrix = matrix self.name = name def __mul__(self, other: "SU2Matrix") -> "SU2Matrix": + """Calculates the dot product of two matrices, and stores them with the combined name. + + Args: + other (SU2Matrix): The other matrix. + + Returns: + SU2Matrix: The product of the two matrices. + """ matrix = np.dot(self.matrix, other.matrix) name = self.name.copy() name.extend(other.name) @@ -30,20 +45,25 @@ def dagger(self) -> "SU2Matrix": def distance(self, other: "SU2Matrix") -> float: """Calculates the operator norm distance between two matrices.""" - diff = self.matrix - other.matrix - return np.linalg.norm(diff) + return np.linalg.norm(self.matrix - other.matrix, 2) def _get_dagger_gate_name(self, name: str): + """Returns the name of the dagger gate.""" if name in SELF_INVERTING_ONE_QUBIT_OP_SET: return name - else: - return ST_GATE_INV_MAP[name] + + return ST_GATE_INV_MAP[name] def __str__(self): return f"name: {self.name}, matrix: {self.matrix}" class TU2Matrix: + """Class representing a 2x2 Traversal Unitary matrix. + + Used for basic approximation in the Solovay-Kitaev algorithm. + """ + def __init__( self, matrix: np.ndarray, name: List[str], identity_group: str, identity_weight: float ): @@ -53,6 +73,15 @@ def __init__( self.identity_weight = identity_weight def __mul__(self, other: "TU2Matrix") -> "TU2Matrix": + """Calculates the dot product of two matrices, and stores them with the combined name. + Adds the identity weight if the identity group is the same. + Updates the identity group and weight if the identity group is different. + Args: + other (TU2Matrix): The other matrix. + + Returns: + TU2Matrix: The product of the two matrices. + """ matrix = np.dot(self.matrix, other.matrix) name = self.name.copy() name.extend(other.name) @@ -67,6 +96,9 @@ def __mul__(self, other: "TU2Matrix") -> "TU2Matrix": return TU2Matrix(matrix, name, identity_group, identity_weight) def can_multiple(self, other: "TU2Matrix"): + """Checks if the two matrices can be multiplied. + If the identity group is the same, the identity weight should be less than 1. + """ if self.identity_group != other.identity_group: return True @@ -75,27 +107,35 @@ def can_multiple(self, other: "TU2Matrix"): return False - def get_diff(self, U): - # trace_diff = np.abs(np.trace(np.dot(self.matrix.conj().T, U) - np.identity(2))) - diff = np.linalg.norm(self.matrix - U, 2) - return diff + def distance(self, other): + """Calculates the operator norm distance between two matrices.""" + return np.linalg.norm(self.matrix - other, 2) def __str__(self): - # return f"name: {self.name}" - return f"name: {self.name}, matrix: {self.matrix}" + return f"""name: {self.name}, + matrix: {self.matrix}, + group: {self.identity_group}, + weight: {self.identity_weight}""" def copy(self) -> "TU2Matrix": + """Returns a copy of the current matrix.""" return TU2Matrix( self.matrix.copy(), self.name.copy(), self.identity_group, self.identity_weight ) -def get_SU2Matrix_for_solovay_kitaev_algorithm(target_basis_set) -> List[SU2Matrix]: +def get_su2matrix_for_solovay_kitaev_algorithm(target_basis_set) -> List[SU2Matrix]: + """Returns a list of SU2Matrix objects for the given basis set. + This list is used for the Solovay-Kitaev algorithm. + """ gate_list = GATE_ENTITY_DATA[target_basis_set] return [SU2Matrix(gate["matrix"], [gate["name"]]) for gate in gate_list] -def get_TU2Matrix_for_basic_approximation(target_basis_set) -> List[TU2Matrix]: +def get_tu2matrix_for_basic_approximation(target_basis_set) -> List[TU2Matrix]: + """Returns a list of TU2Matrix objects for the given basis set. + This list is used for the basic approximation algorithm. + """ whole_gate_list = GATE_ENTITY_DATA[target_basis_set] required_gate_list = [gate for gate in whole_gate_list if gate["used_for_basic_approximation"]] @@ -107,7 +147,22 @@ def get_TU2Matrix_for_basic_approximation(target_basis_set) -> List[TU2Matrix]: ] +def get_identity_weight_group_for_optimizer(target_basis_set): + """Returns the identity weight group for the given basis set. + This is used for the optimization of the gate sequence. + """ + gate_list = GATE_ENTITY_DATA[target_basis_set] + identity_weight_group = {} + + for gate in gate_list: + identity_weight_group[gate["name"]] = { + "group": gate["identity"]["group"], + "weight": gate["identity"]["weight"], + } + + return identity_weight_group + + if __name__ == "__main__": - result = get_SU2Matrix_for_solovay_kitaev_algorithm(BasisSet.CLIFFORD_T) - for i in result: - print(i) + result = get_identity_weight_group_for_optimizer(BasisSet.CLIFFORD_T) + print(result) diff --git a/src/pyqasm/decomposer.py b/src/pyqasm/decomposer.py index e30f4b4d..a12507ae 100644 --- a/src/pyqasm/decomposer.py +++ b/src/pyqasm/decomposer.py @@ -12,9 +12,11 @@ Definition of the Decomposer class """ +import numpy as np import openqasm3.ast as qasm3_ast from openqasm3.ast import BranchingStatement, QuantumGate +from pyqasm.algorithms.solovay_kitaev.solovay_kitaev import solovay_kitaev from pyqasm.exceptions import RebaseError from pyqasm.maps.decomposition_rules import ( DECOMPOSITION_RULES, @@ -31,13 +33,17 @@ class Decomposer: """ @classmethod - def process_gate_statement(cls, gate_name, statement, target_basis_set): + def process_gate_statement( + cls, gate_name, statement, target_basis_set, depth=10, accuracy=1e-6 + ): """Process the gate statement based on the target basis set. Args: gate_name: The name of the gate to process. statement: The statement to process. target_basis_set: The target basis set to rebase the module to. + depth: The depth of the approximation. + accuracy: The accuracy of the approximation. Returns: list: The processed gates based on the target basis set. @@ -55,20 +61,9 @@ def process_gate_statement(cls, gate_name, statement, target_basis_set): rule_list = decomposition_rules[gate_name] processed_gates_list = cls._get_decomposed_gates(rule_list, statement) elif gate_name in {"rx", "ry", "rz"}: - # Use lookup table if ∅ is pi, pi/2 or pi/4 - theta = statement.arguments[0].value - if theta in [CONSTANTS_MAP["pi"], CONSTANTS_MAP["pi"] / 2, CONSTANTS_MAP["pi"] / 4]: - rotational_lookup_rules = ROTATIONAL_LOOKUP_RULES[target_basis_set] - rule_list = rotational_lookup_rules[gate_name][theta] - processed_gates_list = cls._get_decomposed_gates(rule_list, statement) - - # Approximate parameterized gates using Solovay-Kitaev - # Example - - # approx_gates = solovay_kitaev_algo( - # gate_name, statement.arguments[0].value, accuracy=0.01 - # ) - # return approx_gates - pass + processed_gates_list = cls._process_rotational_gate( + gate_name, statement, target_basis_set, depth, accuracy + ) else: # Raise an error if the gate is not supported in the target basis set error = f"Gate '{gate_name}' is not supported in the '{target_basis_set} set'." @@ -77,12 +72,14 @@ def process_gate_statement(cls, gate_name, statement, target_basis_set): return processed_gates_list @classmethod - def process_branching_statement(cls, branching_statement, target_basis_set): + def process_branching_statement(cls, branching_statement, target_basis_set, depth, accuracy): """Process the branching statement based on the target basis set. Args: branching_statement: The branching statement to process. target_basis_set: The target basis set to rebase the module to. + depth: The depth of the approximation. + accuracy: The accuracy of the approximation. Returns: BranchingStatement: The processed branching statement based on the target basis set. @@ -98,7 +95,9 @@ def process_branching_statement(cls, branching_statement, target_basis_set): ) if_block.extend(processed_gates_list) elif isinstance(statement, BranchingStatement): - if_block.append(cls.process_branching_statement(statement, target_basis_set)) + if_block.append( + cls.process_branching_statement(statement, target_basis_set, depth, accuracy) + ) else: if_block.append(statement) @@ -110,7 +109,9 @@ def process_branching_statement(cls, branching_statement, target_basis_set): ) else_block.extend(processed_gates_list) elif isinstance(statement, BranchingStatement): - else_block.append(cls.process_branching_statement(statement, target_basis_set)) + else_block.append( + cls.process_branching_statement(statement, target_basis_set, depth, accuracy) + ) else: else_block.append(statement) @@ -169,3 +170,70 @@ def _get_qubits_for_gate(cls, qubits, rule): else: qubits = [qubits[1]] return qubits + + @classmethod + def _process_rotational_gate(cls, gate_name, statement, target_basis_set, depth, accuracy): + """Process the rotational gates based on the target basis set. + + Args: + gate_name: The name of the gate. + statement: The statement to process. + target_basis_set: The target basis set to rebase the module to. + depth: The depth of the approximation. + accuracy: The accuracy of the approximation. + + Returns: + list: The processed gates based on the target basis set. + """ + theta = statement.arguments[0].value + processed_gates_list = [] + + # Use lookup table if ∅ is pi, pi/2 or pi/4 + if theta in [CONSTANTS_MAP["pi"], CONSTANTS_MAP["pi"] / 2, CONSTANTS_MAP["pi"] / 4]: + rotational_lookup_rules = ROTATIONAL_LOOKUP_RULES[target_basis_set] + rule_list = rotational_lookup_rules[gate_name][theta] + processed_gates_list = cls._get_decomposed_gates(rule_list, statement) + + # Use Solovay-Kitaev's Algorithm for gate approximation + else: + target_matrix = cls._get_target_matrix_for_rotational_gates(gate_name, theta) + approximated_gates = solovay_kitaev(target_matrix, target_basis_set, depth, accuracy) + + for approximated_gate_name in approximated_gates.name: + new_gate = qasm3_ast.QuantumGate( + modifiers=[], + name=qasm3_ast.Identifier(name=approximated_gate_name), + arguments=[], + qubits=statement.qubits, + ) + + processed_gates_list.append(new_gate) + return processed_gates_list + + @classmethod + def _get_target_matrix_for_rotational_gates(cls, gate_name, theta): + """ + Get the target matrix for the rotational gates based on the gate name and theta. + + Args: + gate_name: The name of the gate. + theta: The angle of rotation. + + Returns: + np.ndarray: The target matrix for the rotational gates. + """ + if gate_name == "rx": + target_matrix = np.array( + [ + [np.cos(theta / 2), -1j * np.sin(theta / 2)], + [-1j * np.sin(theta / 2), np.cos(theta / 2)], + ] + ) + elif gate_name == "ry": + target_matrix = np.array( + [[np.cos(theta / 2), -np.sin(theta / 2)], [np.sin(theta / 2), np.cos(theta / 2)]] + ) + else: + target_matrix = np.array([[np.exp(-1j * theta / 2), 0], [0, np.exp(1j * theta / 2)]]) + + return target_matrix diff --git a/src/pyqasm/modules/base.py b/src/pyqasm/modules/base.py index c0a796d0..1cb2dce7 100644 --- a/src/pyqasm/modules/base.py +++ b/src/pyqasm/modules/base.py @@ -527,7 +527,7 @@ def unroll(self, **kwargs): self._unrolled_ast = Program(statements=[], version=self.original_program.version) raise err - def rebase(self, target_basis_set, in_place=True): + def rebase(self, target_basis_set, in_place=True, depth=10, accuracy=1e-6): """Rebase the AST to use a specified target basis set. Will unroll the module if not already done. @@ -535,6 +535,8 @@ def rebase(self, target_basis_set, in_place=True): Args: target_basis_set: The target basis set to rebase the module to. in_place (bool): Flag to indicate if the rebase operation should be done in place. + depth: The depth of the approximation. + accuracy: The accuracy of the approximation. Returns: QasmModule: The module with the gates rebased to the target basis set. @@ -555,7 +557,7 @@ def rebase(self, target_basis_set, in_place=True): # Decompose the gate processed_gates_list = Decomposer.process_gate_statement( - gate_name, statement, target_basis_set + gate_name, statement, target_basis_set, depth, accuracy ) for processed_gate in processed_gates_list: rebased_statements.append(processed_gate) @@ -564,7 +566,9 @@ def rebase(self, target_basis_set, in_place=True): # Recursively process the if_block and else_block rebased_statements.append( - Decomposer.process_branching_statement(statement, target_basis_set) + Decomposer.process_branching_statement( + statement, target_basis_set, depth, accuracy + ) ) else: From d1ce0f440bc924110437e33556600a98dfc8f6ca Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Tue, 11 Mar 2025 15:40:30 +0530 Subject: [PATCH 12/20] Worked on comments --- .../solovay_kitaev/basic_approximation.py | 5 +- src/pyqasm/algorithms/solovay_kitaev/utils.py | 13 ++-- src/pyqasm/decomposer.py | 4 +- src/pyqasm/maps/decomposition_rules.py | 59 ++++--------------- src/pyqasm/maps/gates.py | 17 +++++- 5 files changed, 36 insertions(+), 62 deletions(-) diff --git a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py index 44e2587d..fe37aa0e 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py +++ b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py @@ -27,10 +27,7 @@ def rescursive_traversal( """ accuracy, max_tree_depth, closest_diff, closest_gate, best_gate = params - if current_depth >= max_tree_depth: - return closest_diff, closest_gate, best_gate - - if best_gate: + if current_depth >= max_tree_depth or best_gate: return closest_diff, closest_gate, best_gate for gate in target_gate_set_list: diff --git a/src/pyqasm/algorithms/solovay_kitaev/utils.py b/src/pyqasm/algorithms/solovay_kitaev/utils.py index d55f8ee4..540eebba 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/utils.py +++ b/src/pyqasm/algorithms/solovay_kitaev/utils.py @@ -7,7 +7,7 @@ import numpy as np from pyqasm.elements import BasisSet -from pyqasm.maps.gates import GATE_ENTITY_DATA, SELF_INVERTING_ONE_QUBIT_OP_SET, ST_GATE_INV_MAP +from pyqasm.maps.gates import GATE_OPT_DATA, SELF_INVERTING_ONE_QUBIT_OP_SET, ST_GATE_INV_MAP class SU2Matrix: @@ -102,10 +102,7 @@ def can_multiple(self, other: "TU2Matrix"): if self.identity_group != other.identity_group: return True - if self.identity_weight + other.identity_weight < 1: - return True - - return False + return self.identity_weight + other.identity_weight < 1 def distance(self, other): """Calculates the operator norm distance between two matrices.""" @@ -128,7 +125,7 @@ def get_su2matrix_for_solovay_kitaev_algorithm(target_basis_set) -> List[SU2Matr """Returns a list of SU2Matrix objects for the given basis set. This list is used for the Solovay-Kitaev algorithm. """ - gate_list = GATE_ENTITY_DATA[target_basis_set] + gate_list = GATE_OPT_DATA[target_basis_set] return [SU2Matrix(gate["matrix"], [gate["name"]]) for gate in gate_list] @@ -136,7 +133,7 @@ def get_tu2matrix_for_basic_approximation(target_basis_set) -> List[TU2Matrix]: """Returns a list of TU2Matrix objects for the given basis set. This list is used for the basic approximation algorithm. """ - whole_gate_list = GATE_ENTITY_DATA[target_basis_set] + whole_gate_list = GATE_OPT_DATA[target_basis_set] required_gate_list = [gate for gate in whole_gate_list if gate["used_for_basic_approximation"]] return [ @@ -151,7 +148,7 @@ def get_identity_weight_group_for_optimizer(target_basis_set): """Returns the identity weight group for the given basis set. This is used for the optimization of the gate sequence. """ - gate_list = GATE_ENTITY_DATA[target_basis_set] + gate_list = GATE_OPT_DATA[target_basis_set] identity_weight_group = {} for gate in gate_list: diff --git a/src/pyqasm/decomposer.py b/src/pyqasm/decomposer.py index a12507ae..c1746c4d 100644 --- a/src/pyqasm/decomposer.py +++ b/src/pyqasm/decomposer.py @@ -138,7 +138,9 @@ def _get_decomposed_gates(cls, rule_list, statement): new_gate = qasm3_ast.QuantumGate( modifiers=[], - name=qasm3_ast.Identifier(name=rule["gate"]), + name=qasm3_ast.Identifier( + name=rule if isinstance(rule, str) else rule["gate"] + ), arguments=arguments, qubits=qubits, ) diff --git a/src/pyqasm/maps/decomposition_rules.py b/src/pyqasm/maps/decomposition_rules.py index 4b005ab9..97421b8f 100644 --- a/src/pyqasm/maps/decomposition_rules.py +++ b/src/pyqasm/maps/decomposition_rules.py @@ -99,60 +99,23 @@ class AppliedQubit(Enum): ROTATIONAL_LOOKUP_RULES = { BasisSet.CLIFFORD_T: { "rz": { - CONSTANTS_MAP["pi"]: [ - {"gate": "s"}, - {"gate": "s"}, - ], - CONSTANTS_MAP["pi"] / 2: [{"gate": "s"}], - CONSTANTS_MAP["pi"] / 4: [{"gate": "t"}], + CONSTANTS_MAP["pi"]: ["s", "s"], + CONSTANTS_MAP["pi"] / 2: ["s"], + CONSTANTS_MAP["pi"] / 4: ["t"], }, + # Rx(∅) = H.Rz(∅).H "rx": { - CONSTANTS_MAP["pi"]: [ - {"gate": "h"}, - {"gate": "s"}, - {"gate": "s"}, - {"gate": "h"}, - ], - CONSTANTS_MAP["pi"] - / 2: [ - {"gate": "h"}, - {"gate": "s"}, - {"gate": "h"}, - ], - CONSTANTS_MAP["pi"] - / 4: [ - {"gate": "h"}, - {"gate": "t"}, - {"gate": "h"}, - ], + CONSTANTS_MAP["pi"]: ["h", "s", "s", "h"], + CONSTANTS_MAP["pi"] / 2: ["h", "s", "h"], + CONSTANTS_MAP["pi"] / 4: ["h", "t", "h"], }, + # Ry(∅) = S†.H.Rz(∅).H.S "ry": { - CONSTANTS_MAP["pi"]: [ - {"gate": "sdg"}, - {"gate": "h"}, - {"gate": "s"}, - {"gate": "s"}, - {"gate": "h"}, - {"gate": "s"}, - ], - CONSTANTS_MAP["pi"] - / 2: [ - {"gate": "sdg"}, - {"gate": "h"}, - {"gate": "s"}, - {"gate": "h"}, - {"gate": "s"}, - ], - CONSTANTS_MAP["pi"] - / 4: [ - {"gate": "sdg"}, - {"gate": "h"}, - {"gate": "t"}, - {"gate": "h"}, - {"gate": "s"}, - ], + CONSTANTS_MAP["pi"]: ["sdg", "h", "s", "s", "h", "s"], + CONSTANTS_MAP["pi"] / 2: ["sdg", "h", "s", "h", "s"], + CONSTANTS_MAP["pi"] / 4: ["sdg", "h", "t", "h", "s"], }, } } diff --git a/src/pyqasm/maps/gates.py b/src/pyqasm/maps/gates.py index e038c40b..5bd0e049 100644 --- a/src/pyqasm/maps/gates.py +++ b/src/pyqasm/maps/gates.py @@ -1111,7 +1111,22 @@ def two_qubit_gate_op( BasisSet.CLIFFORD_T: {"h", "t", "s", "cx", "tdg", "sdg"}, } -GATE_ENTITY_DATA = { +# Gate Optimization Data is used by Solovay-Kitaev algorithm to optimize the gate set +# The data is a dictionary with the basis set as the key and a list of dictionaries as the value +# Each dictionary in the list contains the following keys: +# - name: the name of the gate +# - identity: +# The dot product of sequence of some gates becomes Identity matrix. +# The `identity` key is used to determine such sequences of gates. +# It stores the following keys: +# - group: the group to which the gate belongs. +# - weight: the weightage given to that gate. +# If gates belong to smae group, their weights can be added. +# Once the total weight reaches 1, the resultant dot product of sequnce +# becomes Identity Matrix +# - matrix: the matrix representation of the gate +# - used_for_basic_approximation: whether the gate is used for basic approximation +GATE_OPT_DATA = { BasisSet.CLIFFORD_T: [ { "name": "h", From 45b9d2ad325b4ecf63090bb890aaacc9a6793a6c Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Tue, 11 Mar 2025 16:22:11 +0530 Subject: [PATCH 13/20] Updated basic_approximation.py to return only best_gate --- .../solovay_kitaev/basic_approximation.py | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py index fe37aa0e..aeb21ce7 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py +++ b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py @@ -25,10 +25,10 @@ def rescursive_traversal( TU2Matrix: The best approx """ - accuracy, max_tree_depth, closest_diff, closest_gate, best_gate = params + accuracy, max_tree_depth, best_gate = params - if current_depth >= max_tree_depth or best_gate: - return closest_diff, closest_gate, best_gate + if current_depth >= max_tree_depth : + return best_gate for gate in target_gate_set_list: if not approximated_matrix.can_multiple(gate): @@ -39,23 +39,22 @@ def rescursive_traversal( diff = approximated_matrix.distance(target_matrix) if diff < accuracy: best_gate = approximated_matrix.copy() - return closest_diff, closest_gate, best_gate + return best_gate # Update the closest gate if the current one is closer - if diff < closest_diff: - closest_diff = diff - closest_gate = approximated_matrix.copy() + if diff < best_gate.distance(target_matrix): + best_gate = approximated_matrix.copy() - closest_diff, closest_gate, best_gate = rescursive_traversal( + best_gate = rescursive_traversal( target_matrix, approximated_matrix.copy(), target_gate_set_list, current_depth + 1, - (accuracy, max_tree_depth, closest_diff, closest_gate, best_gate), + (accuracy, max_tree_depth, best_gate), ) approximated_matrix = approximated_matrix_copy.copy() - return closest_diff, closest_gate, best_gate + return best_gate def basic_approximation(target_matrix, target_gate_set, accuracy=0.001, max_tree_depth=3): @@ -73,24 +72,24 @@ def basic_approximation(target_matrix, target_gate_set, accuracy=0.001, max_tree approximated_matrix = TU2Matrix(np.identity(2), [], None, None) target_gate_set_list = get_tu2matrix_for_basic_approximation(target_gate_set) current_depth = 0 - closest_diff = float("inf") - closest_gate = None - best_gate = None + best_gate = TU2Matrix(np.identity(2), [], None, None) - params = (accuracy, max_tree_depth, closest_diff, closest_gate, best_gate) + params = (accuracy, max_tree_depth, best_gate) - closest_diff, closest_gate, best_gate = rescursive_traversal( + best_gate = rescursive_traversal( target_matrix, approximated_matrix.copy(), target_gate_set_list, current_depth, params ) + + return best_gate - result = None + # result = None - if best_gate: - result = best_gate.copy() - else: - result = closest_gate.copy() + # if best_gate: + # result = best_gate.copy() + # else: + # result = closest_gate.copy() - return result + # return result if __name__ == "__main__": From c3c0c73cabd64d2087fa8614123e06bbe1123006 Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Tue, 11 Mar 2025 16:52:37 +0530 Subject: [PATCH 14/20] Upadted importing sk algo --- src/pyqasm/algorithms/__init__.py | 5 +++++ src/pyqasm/decomposer.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pyqasm/algorithms/__init__.py b/src/pyqasm/algorithms/__init__.py index e69de29b..a9f38253 100644 --- a/src/pyqasm/algorithms/__init__.py +++ b/src/pyqasm/algorithms/__init__.py @@ -0,0 +1,5 @@ +from pyqasm.algorithms.solovay_kitaev.solovay_kitaev import solovay_kitaev + +__all__ = [ + "solovay_kitaev", +] \ No newline at end of file diff --git a/src/pyqasm/decomposer.py b/src/pyqasm/decomposer.py index c1746c4d..467e6335 100644 --- a/src/pyqasm/decomposer.py +++ b/src/pyqasm/decomposer.py @@ -16,7 +16,7 @@ import openqasm3.ast as qasm3_ast from openqasm3.ast import BranchingStatement, QuantumGate -from pyqasm.algorithms.solovay_kitaev.solovay_kitaev import solovay_kitaev +from pyqasm.algorithms import solovay_kitaev from pyqasm.exceptions import RebaseError from pyqasm.maps.decomposition_rules import ( DECOMPOSITION_RULES, From 7f0d7b4daaaa360cd7dfda3e7bf62adee764702c Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Tue, 11 Mar 2025 16:54:05 +0530 Subject: [PATCH 15/20] Formatted using black --- src/pyqasm/algorithms/__init__.py | 2 +- .../algorithms/solovay_kitaev/basic_approximation.py | 6 +++--- src/pyqasm/decomposer.py | 8 +++----- src/pyqasm/maps/decomposition_rules.py | 2 -- src/pyqasm/maps/gates.py | 4 ++-- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/pyqasm/algorithms/__init__.py b/src/pyqasm/algorithms/__init__.py index a9f38253..375289ce 100644 --- a/src/pyqasm/algorithms/__init__.py +++ b/src/pyqasm/algorithms/__init__.py @@ -2,4 +2,4 @@ __all__ = [ "solovay_kitaev", -] \ No newline at end of file +] diff --git a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py index aeb21ce7..5c77177e 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py +++ b/src/pyqasm/algorithms/solovay_kitaev/basic_approximation.py @@ -27,7 +27,7 @@ def rescursive_traversal( """ accuracy, max_tree_depth, best_gate = params - if current_depth >= max_tree_depth : + if current_depth >= max_tree_depth: return best_gate for gate in target_gate_set_list: @@ -39,7 +39,7 @@ def rescursive_traversal( diff = approximated_matrix.distance(target_matrix) if diff < accuracy: best_gate = approximated_matrix.copy() - return best_gate + return best_gate # Update the closest gate if the current one is closer if diff < best_gate.distance(target_matrix): @@ -79,7 +79,7 @@ def basic_approximation(target_matrix, target_gate_set, accuracy=0.001, max_tree best_gate = rescursive_traversal( target_matrix, approximated_matrix.copy(), target_gate_set_list, current_depth, params ) - + return best_gate # result = None diff --git a/src/pyqasm/decomposer.py b/src/pyqasm/decomposer.py index 467e6335..bb721cb6 100644 --- a/src/pyqasm/decomposer.py +++ b/src/pyqasm/decomposer.py @@ -138,9 +138,7 @@ def _get_decomposed_gates(cls, rule_list, statement): new_gate = qasm3_ast.QuantumGate( modifiers=[], - name=qasm3_ast.Identifier( - name=rule if isinstance(rule, str) else rule["gate"] - ), + name=qasm3_ast.Identifier(name=rule if isinstance(rule, str) else rule["gate"]), arguments=arguments, qubits=qubits, ) @@ -176,14 +174,14 @@ def _get_qubits_for_gate(cls, qubits, rule): @classmethod def _process_rotational_gate(cls, gate_name, statement, target_basis_set, depth, accuracy): """Process the rotational gates based on the target basis set. - + Args: gate_name: The name of the gate. statement: The statement to process. target_basis_set: The target basis set to rebase the module to. depth: The depth of the approximation. accuracy: The accuracy of the approximation. - + Returns: list: The processed gates based on the target basis set. """ diff --git a/src/pyqasm/maps/decomposition_rules.py b/src/pyqasm/maps/decomposition_rules.py index 97421b8f..e9e427e5 100644 --- a/src/pyqasm/maps/decomposition_rules.py +++ b/src/pyqasm/maps/decomposition_rules.py @@ -103,14 +103,12 @@ class AppliedQubit(Enum): CONSTANTS_MAP["pi"] / 2: ["s"], CONSTANTS_MAP["pi"] / 4: ["t"], }, - # Rx(∅) = H.Rz(∅).H "rx": { CONSTANTS_MAP["pi"]: ["h", "s", "s", "h"], CONSTANTS_MAP["pi"] / 2: ["h", "s", "h"], CONSTANTS_MAP["pi"] / 4: ["h", "t", "h"], }, - # Ry(∅) = S†.H.Rz(∅).H.S "ry": { CONSTANTS_MAP["pi"]: ["sdg", "h", "s", "s", "h", "s"], diff --git a/src/pyqasm/maps/gates.py b/src/pyqasm/maps/gates.py index 5bd0e049..d8f8bd9f 100644 --- a/src/pyqasm/maps/gates.py +++ b/src/pyqasm/maps/gates.py @@ -1113,9 +1113,9 @@ def two_qubit_gate_op( # Gate Optimization Data is used by Solovay-Kitaev algorithm to optimize the gate set # The data is a dictionary with the basis set as the key and a list of dictionaries as the value -# Each dictionary in the list contains the following keys: +# Each dictionary in the list contains the following keys: # - name: the name of the gate -# - identity: +# - identity: # The dot product of sequence of some gates becomes Identity matrix. # The `identity` key is used to determine such sequences of gates. # It stores the following keys: From a930da1bcc4984e613a2e062219124d3a1006ea9 Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Tue, 11 Mar 2025 16:59:46 +0530 Subject: [PATCH 16/20] Added module docstring for algorithm module --- src/pyqasm/algorithms/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/pyqasm/algorithms/__init__.py b/src/pyqasm/algorithms/__init__.py index 375289ce..9151c2fb 100644 --- a/src/pyqasm/algorithms/__init__.py +++ b/src/pyqasm/algorithms/__init__.py @@ -1,3 +1,12 @@ +""" +Sub module for quantum algorithms. + +Functions: +---------- + solovay_kitaev: Solovay-Kitaev algorithm for approximating unitary gates. + +""" + from pyqasm.algorithms.solovay_kitaev.solovay_kitaev import solovay_kitaev __all__ = [ From 22f5e2460a38eecbc6ff2129148dc4f6c81f5a54 Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Tue, 11 Mar 2025 17:11:16 +0530 Subject: [PATCH 17/20] Moved get_target_matrix_for_rotational_gates from decomposer.py to maps/gates.py --- src/pyqasm/decomposer.py | 33 ++------------------------------- src/pyqasm/maps/gates.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/pyqasm/decomposer.py b/src/pyqasm/decomposer.py index bb721cb6..8c612e41 100644 --- a/src/pyqasm/decomposer.py +++ b/src/pyqasm/decomposer.py @@ -12,7 +12,6 @@ Definition of the Decomposer class """ -import numpy as np import openqasm3.ast as qasm3_ast from openqasm3.ast import BranchingStatement, QuantumGate @@ -24,7 +23,7 @@ AppliedQubit, ) from pyqasm.maps.expressions import CONSTANTS_MAP -from pyqasm.maps.gates import BASIS_GATE_MAP +from pyqasm.maps.gates import BASIS_GATE_MAP, get_target_matrix_for_rotational_gates class Decomposer: @@ -196,7 +195,7 @@ def _process_rotational_gate(cls, gate_name, statement, target_basis_set, depth, # Use Solovay-Kitaev's Algorithm for gate approximation else: - target_matrix = cls._get_target_matrix_for_rotational_gates(gate_name, theta) + target_matrix = get_target_matrix_for_rotational_gates(gate_name, theta) approximated_gates = solovay_kitaev(target_matrix, target_basis_set, depth, accuracy) for approximated_gate_name in approximated_gates.name: @@ -209,31 +208,3 @@ def _process_rotational_gate(cls, gate_name, statement, target_basis_set, depth, processed_gates_list.append(new_gate) return processed_gates_list - - @classmethod - def _get_target_matrix_for_rotational_gates(cls, gate_name, theta): - """ - Get the target matrix for the rotational gates based on the gate name and theta. - - Args: - gate_name: The name of the gate. - theta: The angle of rotation. - - Returns: - np.ndarray: The target matrix for the rotational gates. - """ - if gate_name == "rx": - target_matrix = np.array( - [ - [np.cos(theta / 2), -1j * np.sin(theta / 2)], - [-1j * np.sin(theta / 2), np.cos(theta / 2)], - ] - ) - elif gate_name == "ry": - target_matrix = np.array( - [[np.cos(theta / 2), -np.sin(theta / 2)], [np.sin(theta / 2), np.cos(theta / 2)]] - ) - else: - target_matrix = np.array([[np.exp(-1j * theta / 2), 0], [0, np.exp(1j * theta / 2)]]) - - return target_matrix diff --git a/src/pyqasm/maps/gates.py b/src/pyqasm/maps/gates.py index d8f8bd9f..f8f2b218 100644 --- a/src/pyqasm/maps/gates.py +++ b/src/pyqasm/maps/gates.py @@ -1241,3 +1241,31 @@ def map_qasm_inv_op_to_callable(op_name: str): InversionOp.INVERT_ROTATION, ) raise ValidationError(f"Unsupported / undeclared QASM operation: {op_name}") + + +def get_target_matrix_for_rotational_gates(gate_name, theta): + """ + Get the target matrix for the rotational gates based on the gate name and theta. + + Args: + gate_name: The name of the gate. + theta: The angle of rotation. + + Returns: + np.ndarray: The target matrix for the rotational gates. + """ + if gate_name == "rx": + target_matrix = np.array( + [ + [np.cos(theta / 2), -1j * np.sin(theta / 2)], + [-1j * np.sin(theta / 2), np.cos(theta / 2)], + ] + ) + elif gate_name == "ry": + target_matrix = np.array( + [[np.cos(theta / 2), -np.sin(theta / 2)], [np.sin(theta / 2), np.cos(theta / 2)]] + ) + else: + target_matrix = np.array([[np.exp(-1j * theta / 2), 0], [0, np.exp(1j * theta / 2)]]) + + return target_matrix From 18c036c9b45fceedbd8bfbf764e8369c0c59ecd5 Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Thu, 20 Mar 2025 15:07:38 +0530 Subject: [PATCH 18/20] Updated importing --- src/pyqasm/algorithms/__init__.py | 2 +- src/pyqasm/algorithms/solovay_kitaev/__init__.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/pyqasm/algorithms/__init__.py b/src/pyqasm/algorithms/__init__.py index 9151c2fb..91f8d6fc 100644 --- a/src/pyqasm/algorithms/__init__.py +++ b/src/pyqasm/algorithms/__init__.py @@ -7,7 +7,7 @@ """ -from pyqasm.algorithms.solovay_kitaev.solovay_kitaev import solovay_kitaev +from .solovay_kitaev import solovay_kitaev __all__ = [ "solovay_kitaev", diff --git a/src/pyqasm/algorithms/solovay_kitaev/__init__.py b/src/pyqasm/algorithms/solovay_kitaev/__init__.py index e69de29b..91f8d6fc 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/__init__.py +++ b/src/pyqasm/algorithms/solovay_kitaev/__init__.py @@ -0,0 +1,14 @@ +""" +Sub module for quantum algorithms. + +Functions: +---------- + solovay_kitaev: Solovay-Kitaev algorithm for approximating unitary gates. + +""" + +from .solovay_kitaev import solovay_kitaev + +__all__ = [ + "solovay_kitaev", +] From 507951fc6e1ac7ff61e96e4184c21c8706fd6110 Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Thu, 20 Mar 2025 15:08:15 +0530 Subject: [PATCH 19/20] Updated unit test cases to handel conversions from lookup table --- tests/qasm3/test_rebase.py | 75 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/qasm3/test_rebase.py b/tests/qasm3/test_rebase.py index 7407ea9e..a4f1538b 100644 --- a/tests/qasm3/test_rebase.py +++ b/tests/qasm3/test_rebase.py @@ -139,6 +139,81 @@ def test_rebase_rotational_cx(input_gates, decomposed_gates): cx q[0], q[1]; """, ), + ( + "rx(pi) q[0];", + """ + h q[0]; + s q[0]; + s q[0]; + h q[0]; + """, + ), + ( + "rx(pi/2) q[0];", + """ + h q[0]; + s q[0]; + h q[0]; + """, + ), + ( + "rx(pi/4) q[0];", + """ + h q[0]; + t q[0]; + h q[0]; + """, + ), + ( + "ry(pi) q[0];", + """ + sdg q[0]; + h q[0]; + s q[0]; + s q[0]; + h q[0]; + s q[0]; + """, + ), + ( + "ry(pi/2) q[0];", + """ + sdg q[0]; + h q[0]; + s q[0]; + h q[0]; + s q[0]; + """, + ), + ( + "ry(pi/4) q[0];", + """ + sdg q[0]; + h q[0]; + t q[0]; + h q[0]; + s q[0]; + """, + ), + ( + "rz(pi) q[0];", + """ + s q[0]; + s q[0]; + """, + ), + ( + "rz(pi/2) q[0];", + """ + s q[0]; + """, + ), + ( + "rz(pi/4) q[0];", + """ + t q[0]; + """, + ), ], ) def test_rebase_clifford_t(input_gates, decomposed_gates): From 4d12c2f689a6872669cbffb942b9f883b7b21d61 Mon Sep 17 00:00:00 2001 From: PranavTupe2000 Date: Tue, 15 Apr 2025 15:09:32 +0530 Subject: [PATCH 20/20] Upadeted code for GC Decompose --- .../algorithms/solovay_kitaev/gc_decompose.py | 59 +++ .../solovay_kitaev/solovay_kitaev.py | 79 ++-- .../algorithms/solovay_kitaev/testing.ipynb | 348 ++++++++++++++++++ 3 files changed, 453 insertions(+), 33 deletions(-) create mode 100644 src/pyqasm/algorithms/solovay_kitaev/gc_decompose.py create mode 100644 src/pyqasm/algorithms/solovay_kitaev/testing.ipynb diff --git a/src/pyqasm/algorithms/solovay_kitaev/gc_decompose.py b/src/pyqasm/algorithms/solovay_kitaev/gc_decompose.py new file mode 100644 index 00000000..99cddeb4 --- /dev/null +++ b/src/pyqasm/algorithms/solovay_kitaev/gc_decompose.py @@ -0,0 +1,59 @@ +import math +from typing import List +import numpy as np +from pyqasm.algorithms.solovay_kitaev.utils import SU2Matrix + +def u_to_bloch(u): + """Compute angle and axis for a unitary.""" + + angle = np.real(np.arccos((u[0, 0] + u[1, 1]) / 2)) + sin = np.sin(angle) + if sin < 1e-10: + axis = [0, 0, 1] + else: + nx = (u[0, 1] + u[1, 0]) / (2j * sin) + ny = (u[0, 1] - u[1, 0]) / (2 * sin) + nz = (u[0, 0] - u[1, 1]) / (2j * sin) + axis = [nx, ny, nz] + return axis, 2 * angle + +def Rotation(vparm: List[float], theta: float, name: str): + """Produce the single-qubit rotation operator.""" + + v = np.asarray(vparm) + if v.shape != (3,) or not math.isclose(v @ v, 1) or not np.all(np.isreal(v)): + raise ValueError('Rotation vector v must be a 3D real unit vector.') + + return np.cos(theta / 2) * np.identity(2) - 1j * np.sin(theta / 2) * (v[0] * np.array([[0.0, 1.0], [1.0, 0.0]]) + v[1] * np.array([[0.0, -1.0j], [1.0j, 0.0]]) + v[2] * np.array([[1.0, 0.0], [0.0, -1.0]])) + + +def RotationX(theta: float): + return Rotation([1.0, 0.0, 0.0], theta, 'Rx') + + +def RotationY(theta: float): + return Rotation([0.0, 1.0, 0.0], theta, 'Ry') + + +def RotationZ(theta: float): + return Rotation([0.0, 0.0, 1.0], theta, 'Rz') + +def gc_decomp(u): + axis, theta = u_to_bloch(u) + + phi = 2.0 * np.arcsin(np.sqrt(np.sqrt((0.5 - 0.5 * np.cos(theta / 2))))) + v = RotationX(phi) + if axis[2] > 0: + w = RotationY(2 * np.pi - phi) + else: + w = RotationY(phi) + + _, ud = np.linalg.eig(u) + + vwvdwd = v @ w @ v.conj().T @ w.conj().T + + s = ud @ vwvdwd.conj().T + + v_hat = s @ v @ s.conj().T + w_hat = s @ w @ s.conj().T + return SU2Matrix(v_hat, []), SU2Matrix(w_hat, []) \ No newline at end of file diff --git a/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py b/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py index 1d97139d..349cc03d 100644 --- a/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py +++ b/src/pyqasm/algorithms/solovay_kitaev/solovay_kitaev.py @@ -7,13 +7,14 @@ import numpy as np from pyqasm.algorithms.solovay_kitaev.basic_approximation import basic_approximation +from pyqasm.algorithms.solovay_kitaev.gc_decompose import gc_decomp from pyqasm.algorithms.solovay_kitaev.optimizer import optimize_gate_sequence from pyqasm.algorithms.solovay_kitaev.utils import ( SU2Matrix, get_su2matrix_for_solovay_kitaev_algorithm, ) from pyqasm.elements import BasisSet - +count = 1093 def group_commutator(a: SU2Matrix, b: SU2Matrix) -> SU2Matrix: """Compute the group commutator [a,b] = aba^{-1}b^{-1}.""" @@ -62,7 +63,10 @@ def decompose_group_element( Returns: Tuple[List[SU2Matrix], float]: The sequence of basic gates and the error. """ - + # global count + # count -= 1 + # print(count) + if depth == 0: best_approx = find_basic_approximation( target.matrix, target_gate_set, use_optimization=use_optimization @@ -79,23 +83,25 @@ def decompose_group_element( if prev_error < accuracy: return prev_sequence, prev_error - error = target * prev_sequence.dagger() + # error = target * prev_sequence.dagger() - # Find Va and Vb such that their group commutator approximates the error - best_v = None - best_w = None - best_error = float("inf") + # # Find Va and Vb such that their group commutator approximates the error + # best_v = None + # best_w = None + # best_error = float("inf") - for v in basic_gates: - for w in basic_gates: - comm = group_commutator(v, w) - curr_error = error.distance(comm) - if curr_error < best_error: - best_error = curr_error - best_v = v - best_w = w + # for v in basic_gates: + # for w in basic_gates: + # comm = group_commutator(v, w) + # curr_error = error.distance(comm) + # if curr_error < best_error: + # best_error = curr_error + # best_v = v + # best_w = w result = prev_sequence + + best_v, best_w = gc_decomp((target * prev_sequence.dagger()).matrix) # Add correction terms if best_v is not None and best_w is not None: @@ -109,7 +115,6 @@ def decompose_group_element( result = group_commutator(v_sequence, w_sequence) * prev_sequence final_error = target.distance(result) - return result, final_error @@ -146,24 +151,32 @@ def solovay_kitaev( return sequence -if __name__ == "__main__": - target_matrix_U = np.array([[0.70711, 0.70711j], [0.70711j, 0.70711]]) +# if __name__ == "__main__": +# target_matrix_U = np.array([[0.70711, 0.70711j], [0.70711j, 0.70711]]) + +# r0 = solovay_kitaev(target_matrix_U, BasisSet.CLIFFORD_T, depth=0) +# print(r0.name) # Output: ['s', 'h', 's'] - r0 = solovay_kitaev(target_matrix_U, BasisSet.CLIFFORD_T, depth=0) - print(r0.name) # Output: ['s', 'h', 's'] +# r1 = solovay_kitaev(target_matrix_U, BasisSet.CLIFFORD_T, depth=1) +# print( +# r1.name +# ) # Output: ['s', 's', 's', 't', 't', 'tdg', 'sdg', 'sdg', 'sdg', 'tdg', 's', 'h', 's'] - r1 = solovay_kitaev(target_matrix_U, BasisSet.CLIFFORD_T, depth=1) - print( - r1.name - ) # Output: ['s', 's', 's', 't', 't', 'tdg', 'sdg', 'sdg', 'sdg', 'tdg', 's', 'h', 's'] +# r2 = solovay_kitaev(target_matrix_U, BasisSet.CLIFFORD_T, depth=2) +# print(r2.name) # Output: ['t', 's', 's', 's', 't', +# # 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', +# # 't', 's', 's', 's', 't', +# # 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', +# # 's', 'h', 's'] - r2 = solovay_kitaev(target_matrix_U, BasisSet.CLIFFORD_T, depth=2) - print(r2.name) # Output: ['t', 's', 's', 's', 't', - # 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', - # 't', 's', 's', 's', 't', - # 'tdg', 'tdg', 'sdg', 'sdg', 'sdg', - # 's', 'h', 's'] +# print(np.allclose(r0.matrix, r1.matrix)) # Output: True +# print(np.allclose(r1.matrix, r2.matrix)) # Output: True +# print(np.allclose(r2.matrix, r0.matrix)) # Output: True - print(np.allclose(r0.matrix, r1.matrix)) # Output: True - print(np.allclose(r1.matrix, r2.matrix)) # Output: True - print(np.allclose(r2.matrix, r0.matrix)) # Output: True +if __name__ == '__main__': + U = np.array([[-0.8987577 +0.j, -0.31607208+0.30386352j],[-0.14188982-0.41485163j, 0.79903828+0.41146473j]]) + r0 = solovay_kitaev(U, BasisSet.CLIFFORD_T, depth=2) + print("----------------") + print(r0.distance(SU2Matrix(U, []))) + print(r0.matrix) + # print(r0.name) diff --git a/src/pyqasm/algorithms/solovay_kitaev/testing.ipynb b/src/pyqasm/algorithms/solovay_kitaev/testing.ipynb new file mode 100644 index 00000000..8f553fae --- /dev/null +++ b/src/pyqasm/algorithms/solovay_kitaev/testing.ipynb @@ -0,0 +1,348 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import qiskit\n", + "from qiskit.circuit.random import random_circuit\n", + "from qiskit.quantum_info import Operator\n", + "import random" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Generating random circuits" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "class CircuitInfo:\n", + " def __init__(self, qc, resultant_unitary, gate_names):\n", + " self.qc = qc\n", + " self.resultant_unitary = resultant_unitary\n", + " self.gate_names = gate_names" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "circuit_list = []\n", + "\n", + "for _ in range(10):\n", + " qc = random_circuit(1, random.randint(1, 20), measure=False)\n", + " resultant_unitary = Operator(qc).data\n", + " gate_names = [instruction.operation.name for instruction in qc.data]\n", + " \n", + " circuit_list.append(CircuitInfo(qc, resultant_unitary, gate_names))" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "circuit_list[0].qc.draw(output='mpl')" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0.70710678+0.j, -0.70710678+0.j],\n", + " [-0.70710678+0.j, -0.70710678+0.j]])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "circuit_list[0].resultant_unitary" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['z', 'x', 'h']" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "circuit_list[0].gate_names" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Performing Testing" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import timeit\n", + "from functools import partial\n", + "\n", + "from pyqasm.algorithms import solovay_kitaev\n", + "from pyqasm.maps.gates import BasisSet" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [], + "source": [ + "def measure_result_and_time(target_matrix_U, target_gate_set):\n", + " result = solovay_kitaev(target_matrix_U, target_gate_set, depth=20)\n", + " \n", + " partial_func = partial(solovay_kitaev, target_matrix_U, target_gate_set, 20)\n", + " avg_exc_time = timeit.timeit(partial_func, number=1)/1\n", + " return result, avg_exc_time" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [], + "source": [ + "def test_and_analyze_sk_algo(circuit_list, target_gate_set):\n", + " for circuit in circuit_list:\n", + " result, avg_exc_time = measure_result_and_time(circuit.resultant_unitary, target_gate_set)\n", + " result_distance = np.linalg.norm(result.matrix - circuit.resultant_unitary, 2)\n", + " \n", + " print(\"------------------------------------------------\")\n", + " print(f\"Execution time: {avg_exc_time}\")\n", + " print(f\"Circuit gates: {circuit.gate_names}\")\n", + " print(f\"Decomposed gates: {result.name}\")\n", + " print(f\"Distance: {result_distance}\")\n", + " print(f\"Is within accuracy: {result_distance < 1e-6}\")\n", + " print(\"Orignal resultant matrix:\")\n", + " print(circuit.resultant_unitary)\n", + " print(\"Decomposed resultant unitary:\")\n", + " print(result.matrix)\n", + " print(\"------------------------------------------------\")" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "------------------------------------------------\n", + "Execution time: 2.4317583000083687\n", + "Circuit gates: ['z', 'x', 'h']\n", + "Decomposed gates: ['t', 't', 't', 't', 'h', 't', 't', 's']\n", + "Distance: 2.7488925102312213e-16\n", + "Is within accuracy: True\n", + "Orignal resultant matrix:\n", + "[[ 0.70710678+0.j -0.70710678+0.j]\n", + " [-0.70710678+0.j -0.70710678+0.j]]\n", + "Decomposed resultant unitary:\n", + "[[ 0.70710678+0.00000000e+00j -0.70710678+0.00000000e+00j]\n", + " [-0.70710678-2.53429949e-17j -0.70710678-5.55111512e-17j]]\n", + "------------------------------------------------\n", + "------------------------------------------------\n", + "Execution time: 4.228998799997498\n", + "Circuit gates: ['r', 'u1']\n", + "Decomposed gates: ['h', 't', 't', 't', 't', 't', 's', 'h', 'sdg', 'tdg', 'tdg', 'tdg', 'tdg', 'tdg', 'h', 's', 's', 'h', 's', 's', 'h', 's', 's', 'h']\n", + "Distance: 0.4046729693889623\n", + "Is within accuracy: False\n", + "Orignal resultant matrix:\n", + "[[-0.8987577 +0.j -0.31607208+0.30386352j]\n", + " [-0.14188982-0.41485163j 0.79903828+0.41146473j]]\n", + "Decomposed resultant unitary:\n", + "[[-0.85355339+0.35355339j -0.14644661+0.35355339j]\n", + " [-0.14644661-0.35355339j 0.85355339+0.35355339j]]\n", + "------------------------------------------------\n", + "------------------------------------------------\n", + "Execution time: 2.119061999997939\n", + "Circuit gates: ['z', 'sdg', 'sdg', 't', 's', 'sx', 'y']\n", + "Decomposed gates: ['s', 'h', 't', 's', 's', 'h', 's', 's', 'h', 's']\n", + "Distance: 2.027528834450315e-16\n", + "Is within accuracy: True\n", + "Orignal resultant matrix:\n", + "[[-0.5 -0.5j 0. +0.70710678j]\n", + " [-0.5 +0.5j -0.70710678+0.j ]]\n", + "Decomposed resultant unitary:\n", + "[[-5.00000000e-01-5.00000000e-01j -1.11855716e-17+7.07106781e-01j]\n", + " [-5.00000000e-01+5.00000000e-01j -7.07106781e-01+1.11855716e-17j]]\n", + "------------------------------------------------\n", + "------------------------------------------------\n", + "Execution time: 2.345279599991045\n", + "Circuit gates: ['r', 's', 'rz', 'sdg']\n", + "Decomposed gates: ['t', 'h', 's', 's', 'h', 's', 's', 's', 'h', 't']\n", + "Distance: 0.27439900654353205\n", + "Is within accuracy: False\n", + "Orignal resultant matrix:\n", + "[[ 0.20618486-0.55826326j -0.59488154+0.54032018j]\n", + " [ 0.59488154+0.54032018j 0.20618486+0.55826326j]]\n", + "Decomposed resultant unitary:\n", + "[[-9.59550172e-18-0.70710678j -5.00000000e-01+0.5j ]\n", + " [ 5.00000000e-01+0.5j 5.25894755e-16+0.70710678j]]\n", + "------------------------------------------------\n", + "------------------------------------------------\n", + "Execution time: 19.87103229999775\n", + "Circuit gates: ['u3', 'ry', 's', 'ry', 'sx', 'ry', 'ry', 'sdg', 'u', 'x', 'rx', 'sxdg', 'id', 'ry', 'u', 'u3']\n", + "Decomposed gates: ['s', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 't', 'h', 't', 'h', 't', 's', 'h']\n", + "Distance: 0.2770351557535887\n", + "Is within accuracy: False\n", + "Orignal resultant matrix:\n", + "[[ 0.62808441+0.45021745j 0.59411116+0.22326252j]\n", + " [ 0.29575769-0.56155286j -0.17038302+0.75376084j]]\n", + "Decomposed resultant unitary:\n", + "[[ 0.70710678+5.00000000e-01j 0.5 -9.06080907e-18j]\n", + " [ 0.35355339-3.53553391e-01j -0.14644661+8.53553391e-01j]]\n", + "------------------------------------------------\n", + "------------------------------------------------\n", + "Execution time: 2.138298299993039\n", + "Circuit gates: ['u2', 'ry', 'h']\n", + "Decomposed gates: ['h', 's', 's', 'h', 't', 'h', 't', 's', 'h', 's']\n", + "Distance: 0.3227556363764241\n", + "Is within accuracy: False\n", + "Orignal resultant matrix:\n", + "[[ 0.92162512+0.21404063j -0.32184159+0.0348101j ]\n", + " [ 0.09123887+0.31059494j 0.55102236+0.76914277j]]\n", + "Decomposed resultant unitary:\n", + "[[ 0.85355339+0.35355339j -0.35355339-0.14644661j]\n", + " [ 0.14644661+0.35355339j 0.35355339+0.85355339j]]\n", + "------------------------------------------------\n", + "------------------------------------------------\n", + "Execution time: 8.068621699989308\n", + "Circuit gates: ['u']\n", + "Decomposed gates: ['s', 'h', 't', 'h', 't', 'h', 's', 's', 't']\n", + "Distance: 0.3480364791371961\n", + "Is within accuracy: False\n", + "Orignal resultant matrix:\n", + "[[ 0.81342817+0.j -0.05970368-0.5785932j ]\n", + " [-0.24774313+0.52626795j -0.76763181-0.26908508j]]\n", + "Decomposed resultant unitary:\n", + "[[ 8.53553391e-01+0.14644661j 3.64046306e-17-0.5j ]\n", + " [-3.53553391e-01+0.35355339j -7.07106781e-01-0.5j ]]\n", + "------------------------------------------------\n", + "------------------------------------------------\n", + "Execution time: 18.017034499993315\n", + "Circuit gates: ['y', 'u1', 'r', 'h', 'z', 'u1', 'p', 'u', 'u2', 's', 'u', 'sx', 't', 'x', 'u2', 'y']\n", + "Decomposed gates: ['s', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'tdg', 'tdg', 'tdg', 'tdg', 's', 't', 'sdg', 'tdg', 'h', 't', 't', 'h', 's', 's', 's', 'h', 't', 't']\n", + "Distance: 0.39865290368990064\n", + "Is within accuracy: False\n", + "Orignal resultant matrix:\n", + "[[-0.06007544-0.01807924j -0.70686923+0.70455658j]\n", + " [ 0.92164167-0.38293696j -0.0063209 +0.06241765j]]\n", + "Decomposed resultant unitary:\n", + "[[-5.19907294e-17+7.49842011e-17j -7.07106781e-01+7.07106781e-01j]\n", + " [ 7.07106781e-01-7.07106781e-01j 3.89002693e-16-3.66009221e-16j]]\n", + "------------------------------------------------\n", + "------------------------------------------------\n", + "Execution time: 23.155897600008757\n", + "Circuit gates: ['z', 'tdg', 'z', 'u2', 't', 'id', 'sxdg', 'sx', 'id', 's', 'y', 'id', 'sx', 't', 'y', 'ry', 'rz']\n", + "Decomposed gates: ['s', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 't', 'tdg', 'h', 's', 's', 'h', 's', 's', 's', 'h', 's', 't']\n", + "Distance: 0.45184583063972\n", + "Is within accuracy: False\n", + "Orignal resultant matrix:\n", + "[[-0.25037996-0.72070377j -0.17476521-0.62237696j]\n", + " [ 0.54982081-0.33998975j -0.67202971+0.36122031j]]\n", + "Decomposed resultant unitary:\n", + "[[-1.36185234e-17-7.07106781e-01j -5.00000000e-01-5.00000000e-01j]\n", + " [ 7.07106781e-01+2.32873552e-16j -5.00000000e-01+5.00000000e-01j]]\n", + "------------------------------------------------\n", + "------------------------------------------------\n", + "Execution time: 30.734558600001037\n", + "Circuit gates: ['u', 'rz', 'p', 's', 'z', 'z', 'p', 'sx', 'tdg', 't', 'rx', 'rx', 'rx']\n", + "Decomposed gates: ['s', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'sdg', 'sdg', 's', 's', 'tdg', 'tdg', 'tdg', 'tdg', 's', 't', 'tdg', 'tdg', 'tdg', 's', 't', 'sdg', 'tdg', 'h', 't', 't', 'h', 's', 'h', 's', 't', 'h', 's']\n", + "Distance: 0.23391882033249825\n", + "Is within accuracy: False\n", + "Orignal resultant matrix:\n", + "[[ 0.53716092+0.49705851j -0.4588246 +0.50385611j]\n", + " [-0.15799653+0.66289372j 0.71324392+0.16398819j]]\n", + "Decomposed resultant unitary:\n", + "[[ 5.00000000e-01+5.00000000e-01j -5.00000000e-01+5.00000000e-01j]\n", + " [-5.98455758e-16+7.07106781e-01j 7.07106781e-01+1.35729377e-16j]]\n", + "------------------------------------------------\n" + ] + } + ], + "source": [ + "test_and_analyze_sk_algo(circuit_list, BasisSet.CLIFFORD_T)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "qbraid-venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}