From 6a49fbb2d9121534a2a060159941cff6a47f15ee Mon Sep 17 00:00:00 2001 From: Niklas Steinmann Date: Tue, 22 Oct 2024 10:22:50 +0200 Subject: [PATCH 01/15] Update test_QAOAMkCS.py Had to make this test easier, since it fails too often... --- tests/test_QAOAMkCS.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_QAOAMkCS.py b/tests/test_QAOAMkCS.py index 84d972bc..7d9658ab 100644 --- a/tests/test_QAOAMkCS.py +++ b/tests/test_QAOAMkCS.py @@ -108,7 +108,7 @@ def G1e2c_bin(): def test_mkcs_5nodes(): G = nx.Graph() - G.add_edges_from([[0,1],[0,4],[1,2],[1,3],[1,4],[2,3],[3,4]]) + G.add_edges_from([[0,1],[0,4],[1,2],[1,4],[2,3]]) num_nodes = len(G.nodes) @@ -159,7 +159,7 @@ def G_bin(): if all(res_str_bin[pair[0]] != res_str_bin[pair[1]] for pair in list(G.edges())): return True - for _ in range(5): + for _ in range(8): if G_bin() == True: break else: From 5d8dfe0ef37cbb7928da1882864ed4469fb7ea30 Mon Sep 17 00:00:00 2001 From: Rene Zander Date: Mon, 28 Oct 2024 14:01:33 +0100 Subject: [PATCH 02/15] Update qiskit_aer --- .../source/reference/Backend Interface/index.rst | 6 +++--- src/qrisp/core/quantum_session.py | 10 +++++----- .../interface/provider_backends/qiskit_backend.py | 12 ++++++------ src/qrisp/interface/qunicorn/backend_server.py | 6 +++--- src/qrisp/interface/virtual_backend.py | 4 ++-- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/documentation/source/reference/Backend Interface/index.rst b/documentation/source/reference/Backend Interface/index.rst index 68324f47..e7806981 100644 --- a/documentation/source/reference/Backend Interface/index.rst +++ b/documentation/source/reference/Backend Interface/index.rst @@ -78,10 +78,10 @@ This class is a wrapper for the VirtualBackend to quickly integrate Qiskit backe :: - from qiskit import Aer + from qiskit_aer import AerSimulator from qrisp.interface import QiskitBackend - qiskit_backend = Aer.get_backend('qasm_simulator') - vrtl_qasm_sim = QiskitBackend(qiskit_backend) + qiskit_backend = AerSimulator() + vrtl_aer_sim = QiskitBackend(qiskit_backend) Naturally, this also works for non-simulator Qiskit backends. diff --git a/src/qrisp/core/quantum_session.py b/src/qrisp/core/quantum_session.py index a312ebbe..7d0aeb11 100644 --- a/src/qrisp/core/quantum_session.py +++ b/src/qrisp/core/quantum_session.py @@ -139,15 +139,15 @@ def __init__(self, backend=None): Examples -------- - We create a QuantumSession with the QASM simulator as default backend and + We create a QuantumSession with the Aer simulator as default backend and register a QuantumFloat in it: - >>> from qiskit import Aer - >>> qasm_sim = Aer.get_backend("qasm_simulator") + >>> from qiskit_aer import AerSimulator + >>> aer_sim = AerSimulator() >>> from qrisp.interface import QiskitBackend - >>> vrtl_qasm_sim = QiskitBackend(qasm_sim) + >>> vrtl_aer_sim = QiskitBackend(aer_sim) >>> from qrisp import QuantumSession, QuantumFloat - >>> qs = QuantumSession(vrtl_qasm_sim) + >>> qs = QuantumSession(vrtl_aer_sim) >>> qf = QuantumFloat(4, qs = qs) diff --git a/src/qrisp/interface/provider_backends/qiskit_backend.py b/src/qrisp/interface/provider_backends/qiskit_backend.py index 38a9c1f9..c2afcaad 100644 --- a/src/qrisp/interface/provider_backends/qiskit_backend.py +++ b/src/qrisp/interface/provider_backends/qiskit_backend.py @@ -28,19 +28,19 @@ class QiskitBackend(VirtualBackend): ---------- backend : Qiskit backend object, optional A Qiskit backend object, which runs QuantumCircuits. The default is - Aer.get_backend('qasm_simulator'). + ``AerSimulator()``. port : int, optional The port to listen. The default is 8079. Examples -------- - We evaluate a QuantumFloat multiplication on the QASM-simulator. + We evaluate a :ref:`QuantumFloat` multiplication on the Aer simulator. >>> from qrisp import QuantumFloat >>> from qrisp.interface import QiskitBackend - >>> from qiskit import Aer - >>> example_backend = QiskitBackend(backend = Aer.get_backend('qasm_simulator')) + >>> from qiskit_aer import AerSimulator + >>> example_backend = QiskitBackend(backend = AerSimulator()) >>> qf = QuantumFloat(4) >>> qf[:] = 3 >>> res = qf*qf @@ -54,9 +54,9 @@ def __init__(self, backend=None, port=None): if backend is None: try: - from qiskit import Aer + from qiskit_aer import AerSimulator - backend = Aer.get_backend("qasm_simulator") + backend = AerSimulator() except ImportError: from qiskit.providers.basic_provider import BasicProvider diff --git a/src/qrisp/interface/qunicorn/backend_server.py b/src/qrisp/interface/qunicorn/backend_server.py index d3bf5d36..ecca764b 100644 --- a/src/qrisp/interface/qunicorn/backend_server.py +++ b/src/qrisp/interface/qunicorn/backend_server.py @@ -74,7 +74,7 @@ class BackendServer: -------- We create a server listening on the localhost IP address using a run function which - prints the token and queries the QASM-simulator. :: + prints the token and queries the Aer-simulator. :: def run_func(qasm_str, shots): @@ -83,8 +83,8 @@ def run_func(qasm_str, shots): qiskit_qc = QuantumCircuit.from_qasm_str(qasm_str) - from qiskit import Aer - qiskit_backend = Aer.get_backend('qasm_simulator') + from qiskit_aer import AerSimulator + qiskit_backend = AerSimulator() #Run Circuit on the Qiskit backend return qiskit_backend.run(qiskit_qc, shots = shots).result().get_counts() diff --git a/src/qrisp/interface/virtual_backend.py b/src/qrisp/interface/virtual_backend.py index fad520d3..6582b3c5 100644 --- a/src/qrisp/interface/virtual_backend.py +++ b/src/qrisp/interface/virtual_backend.py @@ -60,8 +60,8 @@ def run_func(qasm_str, shots = 1000, token = ""): print(qiskit_qc) - from qiskit import Aer - qiskit_backend = Aer.get_backend('qasm_simulator') + from qiskit_aer import AerSimulator + qiskit_backend = AerSimulator() #Run Circuit on the Qiskit backend return qiskit_backend.run(qiskit_qc, shots = shots).result().get_counts() From 7a59090bdae335386312542493d964ad9641e818 Mon Sep 17 00:00:00 2001 From: Rene Zander Date: Mon, 28 Oct 2024 14:20:14 +0100 Subject: [PATCH 03/15] Updated qiskit test --- tests/test_qiskit_backend_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_qiskit_backend_client.py b/tests/test_qiskit_backend_client.py index 6b0ac51d..17d39488 100644 --- a/tests/test_qiskit_backend_client.py +++ b/tests/test_qiskit_backend_client.py @@ -25,8 +25,8 @@ def test_qiskit_backend_client(): try: - from qiskit import Aer - backend = Aer.get_backend("qasm_simulator") + from qiskit_aer import AerSimulator + backend = AerSimulator() except ImportError: from qiskit.providers.basic_provider import BasicProvider backend = BasicProvider().get_backend('basic_simulator') From 00f9ed964e7e6887b9ccf391223d79f3cbb6321f Mon Sep 17 00:00:00 2001 From: positr0nium Date: Fri, 8 Nov 2024 14:13:45 +0100 Subject: [PATCH 04/15] implemented faster memory management algorithm --- .../qc_transformations/memory_management.py | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/src/qrisp/permeability/qc_transformations/memory_management.py b/src/qrisp/permeability/qc_transformations/memory_management.py index b81d865f..90284f73 100644 --- a/src/qrisp/permeability/qc_transformations/memory_management.py +++ b/src/qrisp/permeability/qc_transformations/memory_management.py @@ -18,7 +18,7 @@ import networkx as nx import numpy as np -from numba import njit, prange +from numba import njit def optimize_allocations(qc): from qrisp.permeability import PermeabilityGraph, TerminatorNode @@ -226,42 +226,47 @@ def topological_sort(G, prefer=None, delay=None, sub_sort=nx.topological_sort): @njit(cache=True) -def ancestors_jitted(start_index, indptr, indices, node_amount): - to_do_array = np.zeros(node_amount, dtype=np.byte) - to_do_array[start_index] = 1 - done_array = np.zeros(node_amount, dtype=np.byte) - - stack = 1 - while stack: - node = np.argmax(to_do_array) - to_do_array[node] = 0 - - for i in range(indptr[node], indptr[node + 1]): - new_node = indices[i] - if done_array[new_node] == 0: - to_do_array[new_node] = 1 - stack += 1 - - done_array[node] = 1 - stack -= 1 - - return np.nonzero(done_array)[0] - +def compute_all_ancestors(indptr, indices, node_amount): + # Initialize ancestor sets for all nodes + ancestors = np.zeros((node_amount, node_amount), dtype=np.bool_) + + # Topological sort + in_degree = np.zeros(node_amount, dtype=np.int64) + for i in range(node_amount): + for j in range(indptr[i], indptr[i+1]): + in_degree[indices[j]] += 1 + + queue = [i for i in range(node_amount) if in_degree[i] == 0] + + while queue: + node = queue.pop(0) + ancestors[node, node] = True # A node is its own ancestor + + for i in range(indptr[node], indptr[node+1]): + child = indices[i] + # Add current node and its ancestors to child's ancestors + ancestors[child, :] |= ancestors[node, :] + in_degree[child] -= 1 + if in_degree[child] == 0: + queue.append(child) + + return ancestors -@njit(parallel=True, cache=True) +@njit(cache=True) def ancestors_jitted_wrapper(start_indices, indptr, indices, node_amount): + all_ancestors = compute_all_ancestors(indptr, indices, node_amount) + res = [np.zeros(1, dtype=np.int64)] * len(start_indices) - for i in prange(len(start_indices)): - start_index = start_indices[i] - res[i] = ancestors_jitted(start_index, indptr, indices, node_amount) - + for i, start_index in enumerate(start_indices): + res[i] = np.where(all_ancestors[start_index])[0] + return res def ancestors(dag, start_nodes): node_list = list(dag.nodes()) - sprs_mat = nx.to_scipy_sparse_array(dag, format="csc") + sprs_mat = nx.to_scipy_sparse_array(dag, format="csr") start_indices = [] for i in range(len(dag)): @@ -274,9 +279,9 @@ def ancestors(dag, start_nodes): sprs_mat.indices.astype(np.int32), len(dag), ) - - node_list = [ + + res_node_list = [ [node_list[j] for j in anc_indices] for anc_indices in res_list_indices ] - - return node_list + + return res_node_list From 78a58597c4c1dbcb9506dc58820c8e6499dbcafb Mon Sep 17 00:00:00 2001 From: positr0nium Date: Fri, 8 Nov 2024 20:32:15 +0100 Subject: [PATCH 05/15] added another warning for faulty uncomputations --- src/qrisp/simulator/tensor_factor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/qrisp/simulator/tensor_factor.py b/src/qrisp/simulator/tensor_factor.py index 2b2f66a3..c6659951 100644 --- a/src/qrisp/simulator/tensor_factor.py +++ b/src/qrisp/simulator/tensor_factor.py @@ -222,6 +222,8 @@ def disentangle(self, qubit, warning = False): if len(outcome_index_list) == 1: temp = xp.zeros(2, dtype = self.tensor_array.data.dtype) if outcome_index_list[0] == 1: + if warning: + print("WARNING: Faulty uncomputation found during simulation.") temp[1] = 1 else: temp[0] = 1 From 0dda2dfb12309094f957e14cbe49bda40ee9a569 Mon Sep 17 00:00:00 2001 From: positr0nium Date: Sun, 10 Nov 2024 15:36:27 +0100 Subject: [PATCH 06/15] another small compiler speed up --- .../permeability/qc_transformations/memory_management.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/qrisp/permeability/qc_transformations/memory_management.py b/src/qrisp/permeability/qc_transformations/memory_management.py index 90284f73..186f1b96 100644 --- a/src/qrisp/permeability/qc_transformations/memory_management.py +++ b/src/qrisp/permeability/qc_transformations/memory_management.py @@ -154,7 +154,7 @@ def topological_sort(G, prefer=None, delay=None, sub_sort=nx.topological_sort): # For large scales, finding the ancestors is a bottleneck. We therefore use a # jitted version - if len(G) * len(prefered_nodes) > 10000: + if len(G) * len(prefered_nodes) > 1000: anc_lists = ancestors(G, prefered_nodes) else: anc_lists = [] @@ -268,10 +268,8 @@ def ancestors(dag, start_nodes): sprs_mat = nx.to_scipy_sparse_array(dag, format="csr") - start_indices = [] - for i in range(len(dag)): - if node_list[i] in start_nodes: - start_indices.append(i) + node_inversion_dic = {node_list[i] : i for i in range(len(node_list))} + start_indices = [node_inversion_dic[node] for node in start_nodes] res_list_indices = ancestors_jitted_wrapper( np.array(start_indices).astype(np.int32), From bb4d7fe1a189f9acf440f76eada009c200d02a05 Mon Sep 17 00:00:00 2001 From: positr0nium Date: Mon, 11 Nov 2024 10:26:46 +0100 Subject: [PATCH 07/15] implemented q_int_mult function that performs integer multiplication using a custom quantum adder --- .../alg_primitives/arithmetic/__init__.py | 2 +- .../alg_primitives/arithmetic/ripple_mult.py | 64 +++++++++++-------- tests/test_quantum_arithmetic.py | 49 +++++++++++++- 3 files changed, 85 insertions(+), 30 deletions(-) diff --git a/src/qrisp/alg_primitives/arithmetic/__init__.py b/src/qrisp/alg_primitives/arithmetic/__init__.py index bbea4889..50aa4c91 100644 --- a/src/qrisp/alg_primitives/arithmetic/__init__.py +++ b/src/qrisp/alg_primitives/arithmetic/__init__.py @@ -16,10 +16,10 @@ ********************************************************************************/ """ - from qrisp.alg_primitives.arithmetic.comparisons import * from qrisp.alg_primitives.arithmetic.SBP_arithmetic import * from qrisp.alg_primitives.arithmetic.ripple_division import * +from qrisp.alg_primitives.arithmetic.ripple_mult import * from qrisp.alg_primitives.arithmetic.matrix_multiplication import * from qrisp.alg_primitives.arithmetic.modular_arithmetic import * from qrisp.alg_primitives.arithmetic.adders import * diff --git a/src/qrisp/alg_primitives/arithmetic/ripple_mult.py b/src/qrisp/alg_primitives/arithmetic/ripple_mult.py index 6ea295e3..3bfd521f 100644 --- a/src/qrisp/alg_primitives/arithmetic/ripple_mult.py +++ b/src/qrisp/alg_primitives/arithmetic/ripple_mult.py @@ -18,38 +18,46 @@ from qrisp import cx, x -from qrisp.alg_primitives.arithmetic import create_output_qf, inpl_add - - -def ripple_mult(factor_1, factor_2): +from qrisp.alg_primitives.arithmetic.adders import fourier_adder +from qrisp.qtypes.quantum_float import QuantumFloat + +def q_int_mult(factor_1, factor_2, inpl_adder = fourier_adder, target_qf = None): + + if factor_1.size < factor_2.size: + factor_1, factor_2 = factor_2, factor_1 + + if factor_1.signed or factor_2.signed: raise Exception("Signed ripple multiplication currently not supported") - s = create_output_qf([factor_1, factor_2], op="mul") - - n = factor_1.size - int(factor_1.signed) - - factor_2.exp_shift(n) - - s.init_from(factor_2) - - factor_2.exp_shift(-n) - + n = factor_1.size-1 + if target_qf is None: + s = QuantumFloat(factor_1.size + factor_2.size + 1, + exponent = factor_1.exponent + factor_2.exponent) + for i in range(factor_2.size): + cx(factor_2[i], s[i+1+n]) + + else: + target_qf.extend(1, 0) + s = target_qf + inpl_adder(factor_2, s[n+1:]) + x(s) - - inpl_add(s, factor_2) - - for i in range(n): - cx(factor_1[i], s) - - inpl_add(s, factor_2) - - cx(factor_1[i], s) - factor_2.exp_shift(1) - + inpl_adder(factor_2, s) + + cx(factor_1[0], s) + for i in range(factor_1.size): + + inpl_adder(factor_2, s[i:]) + + if i != factor_1.size-1: + pass + cx(factor_1[i], factor_1[i+1]) + cx(factor_1[i+1], s) + cx(factor_1[i], factor_1[i+1]) + + cx(factor_1[-1], s) x(s) - - factor_2.exp_shift(-n) - s.exp_shift(-1) + s.reduce(s[0], verify = False) return s diff --git a/tests/test_quantum_arithmetic.py b/tests/test_quantum_arithmetic.py index 2b075f3a..6f03a169 100644 --- a/tests/test_quantum_arithmetic.py +++ b/tests/test_quantum_arithmetic.py @@ -132,4 +132,51 @@ def test_arithmetic_helper(qf_0, qf_1, operation): qf = QuantumFloat(4) h(qf) assert qf.get_ev() == 7.5 - \ No newline at end of file + + # Test q_int_mult function + n = 5 + + a = QuantumFloat(n) + b = QuantumFloat(n) + + h(a) + h(b) + + from qrisp import q_int_mult, gidney_adder + c = q_int_mult(a, b, inpl_adder = gidney_adder) + + meas_res = multi_measurement([a,b,c]) + + for a, b, c in meas_res.keys(): + assert a*b == c + + a = QuantumFloat(n) + b = QuantumFloat(n) + s = QuantumFloat(2*n+1) + s[:] = 15 + + h(a) + h(b) + + from qrisp import q_int_mult, gidney_adder + c = q_int_mult(a, b, inpl_adder = gidney_adder, target_qf = s) + + meas_res = multi_measurement([a,b,c]) + + for a, b, c in meas_res.keys(): + assert a*b + 15 == c + + a = QuantumFloat(n) + b = QuantumFloat(n + 4) + + h(a) + h(b) + + from qrisp import q_int_mult, gidney_adder + c = q_int_mult(a, b, inpl_adder = gidney_adder) + + meas_res = multi_measurement([a,b,c]) + + for a, b, c in meas_res.keys(): + assert a*b == c + \ No newline at end of file From 4a7354b6b051ba58820da855081521268603f229 Mon Sep 17 00:00:00 2001 From: positr0nium Date: Mon, 11 Nov 2024 10:46:37 +0100 Subject: [PATCH 08/15] implemented inpl_q_int_mult function which performs in-place multiplication with an arbitrary adder --- .../modular_multiplication.py | 39 ++++++++++++++++++- .../alg_primitives/arithmetic/ripple_mult.py | 10 ++++- tests/test_quantum_arithmetic.py | 15 ++++++- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/qrisp/alg_primitives/arithmetic/modular_arithmetic/modular_multiplication.py b/src/qrisp/alg_primitives/arithmetic/modular_arithmetic/modular_multiplication.py index f34f480d..9efb96f1 100644 --- a/src/qrisp/alg_primitives/arithmetic/modular_arithmetic/modular_multiplication.py +++ b/src/qrisp/alg_primitives/arithmetic/modular_arithmetic/modular_multiplication.py @@ -402,4 +402,41 @@ def semi_cl_inpl_mult(a, X, ctrl = None, treat_invalid = False): a.__class__ = QuantumModulus reduced.delete(verify = True) - return a \ No newline at end of file + return a + +def montgomery_mod_mul(a, b, output_qg = None, inpl_adder = None): + + m = int(np.ceil(np.log2((a.modulus-1)**2)+1)) - a.size + + from qrisp import h, merge, QFT, q_int_mult + if a.modulus != b.modulus: + raise Exception("Tried to multiply two QuantumModulus with differing modulus") + + if output_qg is None: + t = QuantumFloat(a.size + m, signed = True) + h(t) + + else: + if output_qg.modulus != a.modulus: + raise Exception("Output QuantumModulus has incompatible modulus") + + merge(output_qg.qs, a.qs) + output_qg.extend(m, 0) + output_qg.add_sign() + output_qg.reg.insert(0, output_qg.reg.pop(-1)) + + QFT(output_qg, exec_swap = False) + + t = output_qg + + t = q_int_mult(a, b, output_qf = t, inpl_adder = inpl_adder) + + from qrisp import QuantumModulus + t.__class__ = QuantumModulus + t.modulus = a.modulus + t.m = (a.m + b.m) + t.inpl_adder = a.inpl_adder + + t = montgomery_red(t, a, b, a.modulus, m) + + return t diff --git a/src/qrisp/alg_primitives/arithmetic/ripple_mult.py b/src/qrisp/alg_primitives/arithmetic/ripple_mult.py index 3bfd521f..4fe32727 100644 --- a/src/qrisp/alg_primitives/arithmetic/ripple_mult.py +++ b/src/qrisp/alg_primitives/arithmetic/ripple_mult.py @@ -17,7 +17,7 @@ """ -from qrisp import cx, x +from qrisp import cx, x, control from qrisp.alg_primitives.arithmetic.adders import fourier_adder from qrisp.qtypes.quantum_float import QuantumFloat @@ -61,3 +61,11 @@ def q_int_mult(factor_1, factor_2, inpl_adder = fourier_adder, target_qf = None) s.reduce(s[0], verify = False) return s + +def inpl_q_int_mult(operand, cl_int, inpl_adder = fourier_adder): + if not cl_int%2: + raise Exception("In-place multiplication with even integers not supported") + + for i in range(operand.size-1): + with control(operand[operand.size-2-i]): + inpl_adder(cl_int//2, operand[operand.size-1-i:]) \ No newline at end of file diff --git a/tests/test_quantum_arithmetic.py b/tests/test_quantum_arithmetic.py index 6f03a169..81952277 100644 --- a/tests/test_quantum_arithmetic.py +++ b/tests/test_quantum_arithmetic.py @@ -179,4 +179,17 @@ def test_arithmetic_helper(qf_0, qf_1, operation): for a, b, c in meas_res.keys(): assert a*b == c - \ No newline at end of file + + # Test in-place multiplication + from qrisp.alg_primitives.arithmetic import inpl_q_int_mult + n = 7 + a = QuantumFloat(n) + h(a) + b = QuantumFloat(n) + b[:] = a + inpl_q_int_mult(a, 5, inpl_adder = gidney_adder) + + meas_res = multi_measurement([a,b]) + + for a, b in meas_res.keys(): + assert (b*5)%(2**n) == a From d5d9dad1cdfce7c7e172196621557fa5f4ef271a Mon Sep 17 00:00:00 2001 From: positr0nium Date: Mon, 11 Nov 2024 11:43:53 +0100 Subject: [PATCH 09/15] implemented arbitrary adder based modular out of place multiplication --- .../modular_multiplication.py | 25 ++++++----- .../alg_primitives/arithmetic/ripple_mult.py | 2 +- tests/test_modular_arithmetic.py | 44 +++++++++++++------ 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/qrisp/alg_primitives/arithmetic/modular_arithmetic/modular_multiplication.py b/src/qrisp/alg_primitives/arithmetic/modular_arithmetic/modular_multiplication.py index 9efb96f1..44c214e0 100644 --- a/src/qrisp/alg_primitives/arithmetic/modular_arithmetic/modular_multiplication.py +++ b/src/qrisp/alg_primitives/arithmetic/modular_arithmetic/modular_multiplication.py @@ -173,11 +173,20 @@ def montgomery_red(t, a, b, N, m, permeable_if_zero = False): # Perform Montgomery reduction t, u = QREDC(t, N, m) - # Perform the uncomputation as described in the paper - for k in range(len(a)): - with control(a[k]): - t.inpl_adder(-((2**k*b))*modinv(N, 2**(m+1)), u) + if isinstance(b, QuantumFloat): + from qrisp.alg_primitives.arithmetic import inpl_q_int_mult, q_int_mult + + inpl_q_int_mult(u, N%(2**(m+1)), inpl_adder = t.inpl_adder) + + with invert(): + q_int_mult(a, b, inpl_adder = t.inpl_adder, target_qf = u) + else: + # Perform the uncomputation as described in the paper + for k in range(len(a)): + with control(a[k]): + t.inpl_adder(-((2**k*b))*modinv(N, 2**(m+1)), u) + if permeable_if_zero: # cx(t[0], u[-1]) pass @@ -404,7 +413,7 @@ def semi_cl_inpl_mult(a, X, ctrl = None, treat_invalid = False): return a -def montgomery_mod_mul(a, b, output_qg = None, inpl_adder = None): +def montgomery_mod_mul(a, b, output_qg = None): m = int(np.ceil(np.log2((a.modulus-1)**2)+1)) - a.size @@ -414,8 +423,6 @@ def montgomery_mod_mul(a, b, output_qg = None, inpl_adder = None): if output_qg is None: t = QuantumFloat(a.size + m, signed = True) - h(t) - else: if output_qg.modulus != a.modulus: raise Exception("Output QuantumModulus has incompatible modulus") @@ -425,11 +432,9 @@ def montgomery_mod_mul(a, b, output_qg = None, inpl_adder = None): output_qg.add_sign() output_qg.reg.insert(0, output_qg.reg.pop(-1)) - QFT(output_qg, exec_swap = False) - t = output_qg - t = q_int_mult(a, b, output_qf = t, inpl_adder = inpl_adder) + t = q_int_mult(a, b, target_qf = t, inpl_adder = a.inpl_adder) from qrisp import QuantumModulus t.__class__ = QuantumModulus diff --git a/src/qrisp/alg_primitives/arithmetic/ripple_mult.py b/src/qrisp/alg_primitives/arithmetic/ripple_mult.py index 4fe32727..703a1609 100644 --- a/src/qrisp/alg_primitives/arithmetic/ripple_mult.py +++ b/src/qrisp/alg_primitives/arithmetic/ripple_mult.py @@ -40,7 +40,7 @@ def q_int_mult(factor_1, factor_2, inpl_adder = fourier_adder, target_qf = None) else: target_qf.extend(1, 0) s = target_qf - inpl_adder(factor_2, s[n+1:]) + inpl_adder(factor_2[:s.size-n-1], s[n+1:]) x(s) inpl_adder(factor_2, s) diff --git a/tests/test_modular_arithmetic.py b/tests/test_modular_arithmetic.py index ca1eff8e..4625cc2a 100644 --- a/tests/test_modular_arithmetic.py +++ b/tests/test_modular_arithmetic.py @@ -28,7 +28,7 @@ def test_modular_arithmetic(): b = QuantumModulus(N) a[:] = 4 - h(b) + b[:] = {i : 1 for i in range(N)} a += b mes_res = multi_measurement([a,b]) @@ -42,7 +42,7 @@ def test_modular_arithmetic(): a = QuantumModulus(N) b = 12 - h(a) + a[:] = {i : 1 for i in range(N)} temp = a.duplicate(init = True) a += b @@ -60,7 +60,7 @@ def test_modular_arithmetic(): b = QuantumModulus(N) a[:] = 4 - h(b) + b[:] = {i : 1 for i in range(N)} a -= b @@ -75,7 +75,7 @@ def test_modular_arithmetic(): a = QuantumModulus(N) b = 12 - h(a) + a[:] = {i : 1 for i in range(N)} temp = a.duplicate(init = True) a -= b @@ -92,8 +92,8 @@ def test_modular_arithmetic(): a = QuantumModulus(N) b = QuantumModulus(N) - h(a) - h(b) + a[:] = {i : 1 for i in range(N)} + b[:] = {i : 1 for i in range(N)} res = a + b @@ -123,8 +123,8 @@ def test_modular_arithmetic(): a = QuantumModulus(N) b = QuantumModulus(N) - h(a) - h(b) + a[:] = {i : 1 for i in range(N)} + b[:] = {i : 1 for i in range(N)} res = a - b @@ -153,8 +153,8 @@ def test_modular_arithmetic(): a = QuantumModulus(N) b = QuantumModulus(N) - h(a) - h(b) + a[:] = {i : 1 for i in range(N)} + b[:] = {i : 1 for i in range(N)} res = a*b @@ -167,7 +167,7 @@ def test_modular_arithmetic(): a = QuantumModulus(N) - h(a) + a[:] = {i : 1 for i in range(N)} b = 5 res = a*b @@ -185,7 +185,7 @@ def test_modular_arithmetic(): for i in range(1, N): a = QuantumModulus(N) - h(a) + a[:] = {i : 1 for i in range(N)} b = a.duplicate() cx(a, b) @@ -200,7 +200,7 @@ def test_modular_arithmetic(): for i in range(1, N): a = QuantumModulus(N, inpl_adder = qcla) - h(a) + a[:] = {i : 1 for i in range(N)} b = a.duplicate() cx(a, b) @@ -215,7 +215,7 @@ def test_modular_arithmetic(): for i in range(1, N): a = QuantumModulus(N, inpl_adder = gidney_adder) - h(a) + a[:] = {i : 1 for i in range(N)} b = a.duplicate() cx(a, b) qbl = QuantumBool() @@ -234,3 +234,19 @@ def test_modular_arithmetic(): assert (k[0]*i)%N == k[1] else: assert k[0] == k[1] + + # Test arbitrary adder out of place multiplication + + a = QuantumModulus(N, inpl_adder = gidney_adder) + b = QuantumModulus(N, inpl_adder = gidney_adder) + + + a[:] = {i : 1 for i in range(N)} + b[:] = {i : 1 for i in range(N)} + + c = a*b + + meas_res = multi_measurement([a, b, c]) + + for a, b, c in meas_res.keys(): + assert (a*b)%N == c \ No newline at end of file From dfefd36bb5cc22a040348c9a7dc3f8a97f492871 Mon Sep 17 00:00:00 2001 From: positr0nium Date: Mon, 11 Nov 2024 11:58:43 +0100 Subject: [PATCH 10/15] rewrote margolus circuit to use clifford+t --- .../alg_primitives/mcx_algs/circuit_library.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/qrisp/alg_primitives/mcx_algs/circuit_library.py b/src/qrisp/alg_primitives/mcx_algs/circuit_library.py index d1300651..a6c1e425 100644 --- a/src/qrisp/alg_primitives/mcx_algs/circuit_library.py +++ b/src/qrisp/alg_primitives/mcx_algs/circuit_library.py @@ -18,7 +18,7 @@ import numpy as np -from qrisp import QuantumCircuit, RYGate, CZGate, XGate, Instruction +from qrisp import QuantumCircuit, CZGate, XGate, Instruction def ctrl_state_wrap(qc, ctrl_state): res = qc.copy() @@ -51,19 +51,20 @@ def ctrl_state_wrap(qc, ctrl_state): # Margolus gate as described here https://arxiv.org/abs/quant-ph/0312225 and # here https://www.iccs-meeting.org/archive/iccs2022/papers/133530169.pdf margolus_qc = QuantumCircuit(3) -G = RYGate(np.pi / 4) -margolus_qc.append(G, 2) +margolus_qc.sx(2) +margolus_qc.t(2) margolus_qc.cx(1, 2) -margolus_qc.append(G, 2) +margolus_qc.t(2) margolus_qc.cx(0, 2) reduced_margolus_qc = margolus_qc.copy() +reduced_margolus_qc.sx_dg(2) -margolus_qc.append(G.inverse(), 2) +margolus_qc.t_dg(2) margolus_qc.cx(1, 2) -margolus_qc.append(G.inverse(), 2) - +margolus_qc.t_dg(2) +margolus_qc.sx_dg(2) # Ancilla supported multi controlled X gates from https://arxiv.org/pdf/1508.03273.pdf # and https://www.iccs-meeting.org/archive/iccs2022/papers/133530169.pdf From f6349864387b56dee989ae2f33c1ff022ba34642 Mon Sep 17 00:00:00 2001 From: positr0nium Date: Mon, 11 Nov 2024 13:06:12 +0100 Subject: [PATCH 11/15] added sx gate to t_depth_indicator --- src/qrisp/misc/utility.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/qrisp/misc/utility.py b/src/qrisp/misc/utility.py index 166763e8..dfd94caf 100644 --- a/src/qrisp/misc/utility.py +++ b/src/qrisp/misc/utility.py @@ -1874,12 +1874,15 @@ def t_depth_indicator(op, epsilon): "s", "h", "s_dg", + "sx", + "sx_dg", "measure", "reset", "qb_alloc", "qb_dealloc", "barrier", "gphase", + ]: return 0 elif op.name in ["rx", "ry", "rz", "p", "u1"]: From a2dabd9f7d26339ab5affb4d886e77881c1c59c9 Mon Sep 17 00:00:00 2001 From: positr0nium Date: Tue, 12 Nov 2024 16:42:20 +0100 Subject: [PATCH 12/15] memory management is even faster now --- .../alg_primitives/arithmetic/ripple_mult.py | 2 +- .../qc_transformations/memory_management.py | 197 ++++++++---------- 2 files changed, 85 insertions(+), 114 deletions(-) diff --git a/src/qrisp/alg_primitives/arithmetic/ripple_mult.py b/src/qrisp/alg_primitives/arithmetic/ripple_mult.py index 703a1609..abeeb5c6 100644 --- a/src/qrisp/alg_primitives/arithmetic/ripple_mult.py +++ b/src/qrisp/alg_primitives/arithmetic/ripple_mult.py @@ -48,7 +48,7 @@ def q_int_mult(factor_1, factor_2, inpl_adder = fourier_adder, target_qf = None) cx(factor_1[0], s) for i in range(factor_1.size): - inpl_adder(factor_2, s[i:]) + inpl_adder(factor_2[:len(s)-i], s[i:]) if i != factor_1.size-1: pass diff --git a/src/qrisp/permeability/qc_transformations/memory_management.py b/src/qrisp/permeability/qc_transformations/memory_management.py index 186f1b96..ce7c63bc 100644 --- a/src/qrisp/permeability/qc_transformations/memory_management.py +++ b/src/qrisp/permeability/qc_transformations/memory_management.py @@ -129,100 +129,103 @@ def topological_sort(G, prefer=None, delay=None, sub_sort=nx.topological_sort): delay = lambda x: False G = G.copy() - # Collect the prefered nodes - prefered_nodes = [] - prefered_node_indices = {} + delay_nodes = [] - delay_node_indices = {} + prefered_nodes = [] - for n in G.nodes(): - n.processed = False + node_list = list(G.nodes()) + + for i in range(len(node_list)): + n = node_list[i] if n.instr is None: continue - - if prefer(n.instr): - prefered_node_indices[n] = len(prefered_nodes) - prefered_nodes.append(n) - - if delay(n.instr): - delay_node_indices[n] = len(delay_nodes) - delay_nodes.append(n) - - + elif prefer(n.instr): + prefered_nodes.append(i) + elif delay(n.instr): + delay_nodes.append(i) + + sprs_mat = nx.to_scipy_sparse_array(G, format="csr") + + res = toposort_helper( + sprs_mat.indptr, + sprs_mat.indices.astype(np.int32), + len(G), + np.array(delay_nodes, dtype = np.int32), + np.array(prefered_nodes, dtype = np.int32)) + + return [node_list[i] for i in res] - # For large scales, finding the ancestors is a bottleneck. We therefore use a - # jitted version - if len(G) * len(prefered_nodes) > 1000: - anc_lists = ancestors(G, prefered_nodes) - else: - anc_lists = [] - for i in range(len(prefered_nodes)): - anc_lists.append(list(nx.ancestors(G, prefered_nodes[i]))) - node_ancs = { - prefered_nodes[i]: anc_lists[i] for i in range(len(prefered_nodes)) - } +@njit(cache = True) +def toposort_helper(indptr, indices, node_amount, delay_nodes, prefered_nodes): + # This array returns a graph that reflects all ancestor relations + # i.e. ancestor_graph[42] is True at all ancestors of node 42 + ancestor_graph = compute_all_ancestors(indptr, indices, node_amount) - # We sort the nodes in order to prevent non-deterministic compilation behavior - # prefered_nodes.sort(key=lambda x: len(node_ancs[x]) + 1/hash(x.instr)) + n = prefered_nodes.size + m = delay_nodes.size - # Determine the required delay nodes for each prefered nodes + # This array will contain the ancestor relations between the + # prefered/delay nodes + dependency_matrix = np.zeros((n, m), dtype = np.int8) + + # Fill with information from ancestor_graph + for i in range(n): + for j in range(m): + if ancestor_graph[prefered_nodes[i], delay_nodes[j]]: + dependency_matrix[i, j] = 1 - # For this we set up a matrix with boolean entriesthat indicates which - # delay nodes are required to execute a prefered node. - dependency_matrix = np.zeros((len(prefered_nodes), len(delay_nodes)), dtype = np.int8) + # This array will contain the result + res = np.zeros(node_amount, dtype = np.int32) - # Fill the matrix - for n in prefered_nodes: - n_index = prefered_node_indices[n] - for k in node_ancs[n]: - if k.instr: - if delay(k.instr): - dependency_matrix[n_index, delay_node_indices[k]] = 1 + # This array array tracks which nodes have not yet been processed. + # It is initialized to all True because no nodes have been processed yet. + remaining_nodes = np.ones(node_amount, dtype = np.int8) - # Generate linearization - lin = [] - - while prefered_nodes: - - # Find the node with least requirements - required_delay_nodes = np.sum(dependency_matrix, axis = 1) - prefered_node_index_array = np.array(list(map(lambda n : prefered_node_indices[n], prefered_nodes)), dtype = np.int32) - min_node_index = np.argmin(required_delay_nodes[prefered_node_index_array]) - - node = prefered_nodes.pop(min_node_index) - ancs = [] - - # Find the ancestors subgraph of nodes that have not been processed yet - for n in node_ancs[node] + [node]: - if n.processed: - continue - else: - n.processed = True - ancs.append(n) - sub_graph = G.subgraph(ancs) - - # Generate the linearization - lin += list(sub_sort(sub_graph)) - - # Update the depedency matrix - dependency_matrix = np.clip(dependency_matrix - dependency_matrix[prefered_node_indices[n], :], 0, 1) - - # Linearize the remainder - remainder = [] - for n in G.nodes(): - if n.processed: - continue - else: - n.processed = True - remainder.append(n) - - # lin += list(sub_sort(G)) - lin += list(sub_sort(G.subgraph(remainder))) + # This integer will contain the amount of nodes that have been processed + node_counter = 0 + + if m != 0: + for i in range(n): + # For each prefer nodes we compute how many delay nodes are required. + required_delay_nodes = np.sum(dependency_matrix, axis = 1) + + # We determine the prefer node that requires the least delay nodes + min_node_index = np.argmin(required_delay_nodes) + prefer_node = prefered_nodes[min_node_index] + + # We determine the ancestor nodes of this node that have + # not been processed yet + to_be_processed = ancestor_graph[prefer_node,:] & remaining_nodes + ancestor_indices = np.nonzero(to_be_processed)[0] + + # We insert the nodes in the result array. + # We can assume that order of the nodes induces by their numbering + # is already a topological ordering. Therefore inserting them in + # order is also a topological sub sort. + res[node_counter:node_counter+len(ancestor_indices)] = ancestor_indices + node_counter += len(ancestor_indices) + + # Mark the nodes as processed + remaining_nodes[ancestor_indices] = 0 + + + # Update the depedency matrix: All delay nodes that have been processed + # don't need to be considered again for all following iterations, + # we therefore remove them from the other columns + dependency_matrix = np.clip(dependency_matrix - dependency_matrix[min_node_index, :], 0, 1) + + # Finaly we set all nodes in the processed column to 1 so this column + # is not processed again. + dependency_matrix[min_node_index, :] = 1 - return lin + # Insert the remaining nodes + res[node_counter:] = np.nonzero(remaining_nodes)[0] + + # return the result + return res @njit(cache=True) @@ -250,36 +253,4 @@ def compute_all_ancestors(indptr, indices, node_amount): if in_degree[child] == 0: queue.append(child) - return ancestors - -@njit(cache=True) -def ancestors_jitted_wrapper(start_indices, indptr, indices, node_amount): - all_ancestors = compute_all_ancestors(indptr, indices, node_amount) - - res = [np.zeros(1, dtype=np.int64)] * len(start_indices) - for i, start_index in enumerate(start_indices): - res[i] = np.where(all_ancestors[start_index])[0] - - return res - - -def ancestors(dag, start_nodes): - node_list = list(dag.nodes()) - - sprs_mat = nx.to_scipy_sparse_array(dag, format="csr") - - node_inversion_dic = {node_list[i] : i for i in range(len(node_list))} - start_indices = [node_inversion_dic[node] for node in start_nodes] - - res_list_indices = ancestors_jitted_wrapper( - np.array(start_indices).astype(np.int32), - sprs_mat.indptr, - sprs_mat.indices.astype(np.int32), - len(dag), - ) - - res_node_list = [ - [node_list[j] for j in anc_indices] for anc_indices in res_list_indices - ] - - return res_node_list + return ancestors \ No newline at end of file From 2d8f881fa50df3b0292146dda1a602ed5b4dc609 Mon Sep 17 00:00:00 2001 From: positr0nium Date: Tue, 12 Nov 2024 18:52:56 +0100 Subject: [PATCH 13/15] fixed a bug within the lightcone reduction call of topological_sort --- src/qrisp/permeability/qc_transformations/memory_management.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qrisp/permeability/qc_transformations/memory_management.py b/src/qrisp/permeability/qc_transformations/memory_management.py index ce7c63bc..14ba9531 100644 --- a/src/qrisp/permeability/qc_transformations/memory_management.py +++ b/src/qrisp/permeability/qc_transformations/memory_management.py @@ -126,7 +126,7 @@ def topological_sort(G, prefer=None, delay=None, sub_sort=nx.topological_sort): prefer = lambda x: False if delay is None: - delay = lambda x: False + delay = lambda x: True G = G.copy() From d92f36f9f0ed804e1cea6a9dd1b41a70e489777f Mon Sep 17 00:00:00 2001 From: positr0nium Date: Wed, 13 Nov 2024 23:55:07 +0100 Subject: [PATCH 14/15] implemented sparse matrix based memory management to mitigate ram requirements --- .../qc_transformations/memory_management.py | 129 +++++++++++++++++- 1 file changed, 123 insertions(+), 6 deletions(-) diff --git a/src/qrisp/permeability/qc_transformations/memory_management.py b/src/qrisp/permeability/qc_transformations/memory_management.py index 14ba9531..8adeca2f 100644 --- a/src/qrisp/permeability/qc_transformations/memory_management.py +++ b/src/qrisp/permeability/qc_transformations/memory_management.py @@ -19,6 +19,7 @@ import networkx as nx import numpy as np from numba import njit +import psutil def optimize_allocations(qc): from qrisp.permeability import PermeabilityGraph, TerminatorNode @@ -155,14 +156,20 @@ def topological_sort(G, prefer=None, delay=None, sub_sort=nx.topological_sort): np.array(prefered_nodes, dtype = np.int32)) return [node_list[i] for i in res] - -@njit(cache = True) def toposort_helper(indptr, indices, node_amount, delay_nodes, prefered_nodes): + if psutil.virtual_memory().available/2 < node_amount**2: + return toposort_helper_sparse(indptr, indices, node_amount, delay_nodes, prefered_nodes) + else: + return toposort_helper_dense(indptr, indices, node_amount, delay_nodes, prefered_nodes) + + +@njit(cache = True) +def toposort_helper_dense(indptr, indices, node_amount, delay_nodes, prefered_nodes): # This array returns a graph that reflects all ancestor relations # i.e. ancestor_graph[42] is True at all ancestors of node 42 - ancestor_graph = compute_all_ancestors(indptr, indices, node_amount) + ancestor_graph = compute_all_ancestors_dense(indptr, indices, node_amount) n = prefered_nodes.size m = delay_nodes.size @@ -176,7 +183,7 @@ def toposort_helper(indptr, indices, node_amount, delay_nodes, prefered_nodes): for j in range(m): if ancestor_graph[prefered_nodes[i], delay_nodes[j]]: dependency_matrix[i, j] = 1 - + # This array will contain the result res = np.zeros(node_amount, dtype = np.int32) @@ -199,6 +206,7 @@ def toposort_helper(indptr, indices, node_amount, delay_nodes, prefered_nodes): # We determine the ancestor nodes of this node that have # not been processed yet to_be_processed = ancestor_graph[prefer_node,:] & remaining_nodes + ancestor_indices = np.nonzero(to_be_processed)[0] # We insert the nodes in the result array. @@ -229,7 +237,7 @@ def toposort_helper(indptr, indices, node_amount, delay_nodes, prefered_nodes): @njit(cache=True) -def compute_all_ancestors(indptr, indices, node_amount): +def compute_all_ancestors_dense(indptr, indices, node_amount): # Initialize ancestor sets for all nodes ancestors = np.zeros((node_amount, node_amount), dtype=np.bool_) @@ -253,4 +261,113 @@ def compute_all_ancestors(indptr, indices, node_amount): if in_degree[child] == 0: queue.append(child) - return ancestors \ No newline at end of file + return ancestors + + +@njit(cache=True) +def compute_all_ancestors_sparse(indptr, indices, node_amount): + # Initialize ancestor sets for all nodes using a list of sets + ancestors = [set() for _ in range(node_amount)] + + # Topological sort + in_degree = np.zeros(node_amount, dtype=np.int64) + for i in range(node_amount): + for j in range(indptr[i], indptr[i+1]): + in_degree[indices[j]] += 1 + + queue = [i for i in range(node_amount) if in_degree[i] == 0] + + while queue: + node = queue.pop(0) + ancestors[node].add(node) # A node is its own ancestor + + for i in range(indptr[node], indptr[node+1]): + child = indices[i] + # Add current node and its ancestors to child's ancestors + ancestors[child].update(ancestors[node]) + in_degree[child] -= 1 + if in_degree[child] == 0: + queue.append(child) + + return ancestors + +# Example usage: +# indptr = np.array([...]) +# indices = np.array([...]) +# node_amount = ... +# ancestors_sparse = compute_all_ancestors_sparse(indptr, indices, node_amount) + +# @njit(cache = True) +def toposort_helper_sparse(indptr, indices, node_amount, delay_nodes, prefered_nodes): + # This array returns a graph that reflects all ancestor relations + # i.e. ancestor_graph[42] is True at all ancestors of node 42 + ancestor_graph_sparse = compute_all_ancestors_sparse(indptr, indices, node_amount) + + n = prefered_nodes.size + m = delay_nodes.size + + # This array will contain the ancestor relations between the + # prefered/delay nodes + dependency_matrix = np.zeros((n, m), dtype = np.int8) + + delay_nodes_inv = -np.ones(node_amount, np.int32) + for i in range(len(delay_nodes)): + delay_nodes_inv[delay_nodes[i]] = i + + for i in range(n): + for j in ancestor_graph_sparse[prefered_nodes[i]]: + if delay_nodes_inv[j] != -1: + dependency_matrix[i, delay_nodes_inv[j]] = 1 + + # This array will contain the result + res = np.zeros(node_amount, dtype = np.int32) + + # This array array tracks which nodes have not yet been processed. + # It is initialized to all True because no nodes have been processed yet. + remaining_nodes = np.ones(node_amount, dtype = np.int8) + + # This integer will contain the amount of nodes that have been processed + node_counter = 0 + + if m != 0: + for i in range(n): + # For each prefer nodes we compute how many delay nodes are required. + required_delay_nodes = np.sum(dependency_matrix, axis = 1) + + # We determine the prefer node that requires the least delay nodes + min_node_index = np.argmin(required_delay_nodes) + prefer_node = prefered_nodes[min_node_index] + + # We determine the ancestor nodes of this node that have + # not been processed yet + to_be_processed = np.zeros(remaining_nodes.size, dtype = np.int8) + for k in ancestor_graph_sparse[prefer_node]: + to_be_processed[k] = remaining_nodes[k] + + ancestor_indices = np.nonzero(to_be_processed)[0] + + # We insert the nodes in the result array. + # We can assume that order of the nodes induces by their numbering + # is already a topological ordering. Therefore inserting them in + # order is also a topological sub sort. + res[node_counter:node_counter+len(ancestor_indices)] = ancestor_indices + node_counter += len(ancestor_indices) + + # Mark the nodes as processed + remaining_nodes[ancestor_indices] = 0 + + + # Update the depedency matrix: All delay nodes that have been processed + # don't need to be considered again for all following iterations, + # we therefore remove them from the other columns + dependency_matrix = np.clip(dependency_matrix - dependency_matrix[min_node_index, :], 0, 1) + + # Finaly we set all nodes in the processed column to 1 so this column + # is not processed again. + dependency_matrix[min_node_index, :] = 1 + + # Insert the remaining nodes + res[node_counter:] = np.nonzero(remaining_nodes)[0] + + # return the result + return res From 4d9efbd796ad15add2c25bad45e5993fe5606c24 Mon Sep 17 00:00:00 2001 From: positr0nium Date: Thu, 14 Nov 2024 12:11:50 +0100 Subject: [PATCH 15/15] fixed a bug in the sparse memory management algorithm --- .../permeability/qc_transformations/memory_management.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/qrisp/permeability/qc_transformations/memory_management.py b/src/qrisp/permeability/qc_transformations/memory_management.py index 8adeca2f..f47a5b8b 100644 --- a/src/qrisp/permeability/qc_transformations/memory_management.py +++ b/src/qrisp/permeability/qc_transformations/memory_management.py @@ -267,7 +267,7 @@ def compute_all_ancestors_dense(indptr, indices, node_amount): @njit(cache=True) def compute_all_ancestors_sparse(indptr, indices, node_amount): # Initialize ancestor sets for all nodes using a list of sets - ancestors = [set() for _ in range(node_amount)] + ancestors = [set([i]) for i in np.arange(node_amount, dtype = np.int32)] # Topological sort in_degree = np.zeros(node_amount, dtype=np.int64) @@ -279,7 +279,6 @@ def compute_all_ancestors_sparse(indptr, indices, node_amount): while queue: node = queue.pop(0) - ancestors[node].add(node) # A node is its own ancestor for i in range(indptr[node], indptr[node+1]): child = indices[i] @@ -297,7 +296,7 @@ def compute_all_ancestors_sparse(indptr, indices, node_amount): # node_amount = ... # ancestors_sparse = compute_all_ancestors_sparse(indptr, indices, node_amount) -# @njit(cache = True) +@njit(cache = True) def toposort_helper_sparse(indptr, indices, node_amount, delay_nodes, prefered_nodes): # This array returns a graph that reflects all ancestor relations # i.e. ancestor_graph[42] is True at all ancestors of node 42