def test_solve_inhomogeneous_equation(self): b = Mat2([[1], [1], [0], [0], [0]]) x = self.m3.solve(b) self.assertEqual(self.m3 * x, b) b = Mat2([[1], [0], [1], [1], [0]]) x = self.m4.solve(b) self.assertEqual(self.m4 * x, b)
def setUp(self): self.m1 = Mat2([[1, 0], [1, 1]]) self.m2 = Mat2([[1, 1], [1, 1]]) self.m3 = Mat2([[1, 0, 1, 1, 0], [1, 1, 1, 0, 0], [1, 1, 0, 0, 1], [0, 1, 0, 1, 0], [0, 0, 1, 1, 0]]) self.m4 = Mat2([[1, 0, 1, 0, 0], [0, 1, 1, 0, 0], [1, 1, 0, 0, 1], [0, 1, 0, 1, 0], [0, 0, 0, 1, 1]])
def update_matrix(self): self.matrix = Mat2(np.identity(self.n_qubits)) for gate in self.gates: if hasattr(gate, "name") and gate.name == "CNOT": self.matrix.row_add(gate.control, gate.target) else: print("Warning: CNOT tracker can only be used for circuits with only CNOT gates!")
def Ariannes_synth(self, architecture, full=True, depth_aware=False, **kwargs): kwargs["full_reduce"] = True #print("------------ START! -----------------------") if architecture is None: architecture = create_architecture(FULLY_CONNECTED, n_qubits=len(self.out_par[0])) n_qubits = architecture.n_qubits # Obtain the parities parities_to_reach = self.all_parities # Make a matrix from the parities matrix = Mat2([[int(parity[i]) for parity in parities_to_reach] for i in range(architecture.n_qubits)] ) circuit = CNOT_tracker(architecture.n_qubits) cols_to_reach = self._check_columns(matrix, circuit, [i for i in range(len(parities_to_reach))], parities_to_reach) self.prev_rows = None def place_cnot(control, target): # Place the CNOT on the circuit circuit.add_gate(CNOT(control, target)) # Adjust the matrix accordingly - reversed elementary row operations matrix.row_add(target, control) def base_recurse(cols_to_use, qubits_to_use): if qubits_to_use != [] and cols_to_use != []: # Select edge qubits qubits = architecture.non_cutting_vertices(qubits_to_use) # Pick the qubit where the recursion split will be most skewed. chosen_row = max(qubits, key=lambda q: max([len([col for col in cols_to_use if matrix.data[q][col] == i]) for i in [1, 0]], default=-1)) # Split the column into 1s and 0s in that row cols1 = [col for col in cols_to_use if matrix.data[chosen_row][col] == 1] cols0 = [col for col in cols_to_use if matrix.data[chosen_row][col] == 0] base_recurse(cols0, [q for q in qubits_to_use if q != chosen_row]) one_recurse(cols1, qubits_to_use, chosen_row) def one_recurse(cols_to_use, qubits_to_use, qubit): if cols_to_use != []: neighbors = [q for q in architecture.get_neighboring_qubits(qubit) if q in qubits_to_use ] if neighbors == []: print(qubits_to_use, qubit) print(matrix) exit(42) chosen_neighbor = max(neighbors, key=lambda q: len([col for col in cols_to_use if matrix.data[q][col] == 1])) # Place CNOTs if you still need to extract columns if sum([matrix.data[chosen_neighbor][c] for c in cols_to_use]) != 0: # Check if adding the cnot is useful place_cnot(qubit, chosen_neighbor) # Might have changed the matrix. cols_to_use = self._check_columns(matrix, circuit, cols_to_use, parities_to_reach) else: # Will never change the matrix place_cnot(chosen_neighbor, qubit) place_cnot(qubit, chosen_neighbor) # Since the neighbor was all zeros, this is effectively a swap and no columns need to be checked. # Split the column into 1s and 0s in that row cols0 = [col for col in cols_to_use if matrix.data[qubit][col] == 0] cols1 = [col for col in cols_to_use if matrix.data[qubit][col] == 1] base_recurse(cols0, [q for q in qubits_to_use if q != qubit]) one_recurse(cols1, qubits_to_use, qubit) base_recurse(cols_to_reach, [i for i in range(n_qubits)]) if full: # Calculate the final parity that needs to be added from the circuit and self.out_par self._obtain_final_parities(circuit, architecture, **kwargs) # Return the circuit circuit.n_gadgets = len(self.zphases.keys()) return circuit, [i for i in range(architecture.n_qubits)], [i for i in range(architecture.n_qubits)]
def gray_synth(self, architecture, full=True, **kwargs): if architecture is None: architecture = create_architecture(FULLY_CONNECTED, n_qubits=len(self.out_par[0])) n_qubits = architecture.n_qubits # Obtain the parities parities_to_reach = self.all_parities # Make a matrix from the parities matrix = Mat2([[int(parity[i]) for parity in parities_to_reach] for i in range(n_qubits)] ) circuit = CNOT_tracker(n_qubits) # Make a stack - aka use the python stack >^.^< def recurse(cols_to_use, qubits_to_use, phase_qubit): # Arguments from the original paper # Check for finished columns cols_to_use = self._check_columns(matrix, circuit, cols_to_use, parities_to_reach) if cols_to_use != [] and qubits_to_use != []: # Find all qubits (rows) with only 1s on the allowed parities (cols_to_use) qubits = [i for i in qubits_to_use if sum([matrix.data[i][j] for j in cols_to_use]) == len(cols_to_use)] if len(qubits) > 1 and phase_qubit is not None: # Pick the column with the most 1s to extrac the steiner tree with column = max(cols_to_use, key=lambda c: sum([row[c] for row in matrix.data])) col = [1 if i in qubits else 0 for i in range(n_qubits)] cnots = list(steiner_reduce_column(architecture, col, phase_qubit, set(qubits + [phase_qubit]), [i for i in range(n_qubits)], [], upper=True)) # For each returned CNOT: for target, control in cnots: # Place the CNOT on the circuit circuit.add_gate(CNOT(control, target)) # Adjust the matrix accordingly - reversed elementary row operations matrix.row_add(target, control) # Keep track of the parities in the circuit - normal elementary row operations cols_to_use = self._check_columns(matrix, circuit, cols_to_use, parities_to_reach) # After placing the cnots do recursion if len(cols_to_use) > 0: # Choose a row to split on if len(cols_to_use) == 1: # Hack because it runs into infinite recursion otherwise, because we do not remove the chosen_row from qubits_to_use # We do not remove the qubit, because it will skip columns # It skips columns because it runs out of qubits before all columns are finished for some reason - could not find the bug qubits = [i for i in qubits_to_use if sum([matrix.data[i][j] for j in cols_to_use]) == 1] else: # Ignore rows that are currently all 0s or all 1s qubits = [i for i in qubits_to_use if sum([matrix.data[i][j] for j in cols_to_use]) not in [0, len(cols_to_use)]] # Pick the qubit where the recursion split will be most skewed. chosen_row = max(qubits, key=lambda q: max([len([col for col in cols_to_use if matrix.data[q][col] == i]) for i in [1,0]], default=-1)) # Split the column into 1s and 0s in that row cols1 = [col for col in cols_to_use if matrix.data[chosen_row][col] == 1] cols0 = [col for col in cols_to_use if matrix.data[chosen_row][col] == 0] #qubits_to_use = [i for i in qubits_to_use if i != chosen_row] recurse(cols0, qubits_to_use, phase_qubit) recurse(cols1, qubits_to_use, phase_qubit if phase_qubit is not None else chosen_row) # Put the base case into the python stack recurse([i for i in range(len(parities_to_reach))], [i for i in range(n_qubits)], None) if full: # Calculate the final parity that needs to be added from the circuit and self.out_par self._obtain_final_parities(circuit, architecture, **kwargs) # Return the circuit circuit.n_gadgets = len(self.zphases.keys()) return circuit, [i for i in range(architecture.n_qubits)], [i for i in range(architecture.n_qubits)]
def _obtain_final_parities(self, circuit, architecture, **kwargs): # Calculate the final parity that needs to be added from the circuit and self.out_par current_parities = circuit.matrix output_parities = Mat2([[int(v) for v in row] for row in self.out_par]) last_parities = output_parities*current_parities.inverse() # Do steiner-gauss to calculate necessary CNOTs and add those to the circuit. cnots = CNOT_tracker(architecture.n_qubits) gauss(last_parities, architecture, y=cnots, **kwargs) for cnot in cnots.gates: circuit.add_gate(cnot)
def build_random_parity_map(qubits, n_cnots, circuit=None): """ Builds a random parity map. :param qubits: The number of qubits that participate in the parity map :param n_cnots: The number of CNOTs in the parity map :param circuit: A (list of) circuit object(s) that implements a row_add() method to add the generated CNOT gates [optional] :return: a 2D numpy array that represents the parity map. """ if circuit is None: circuit = [] if not isinstance(circuit, list): circuit = [circuit] g = generate_cnots(qubits=qubits, depth=n_cnots) c = Circuit.from_graph(g) matrix = Mat2(np.identity(qubits)) for gate in c.gates: matrix.row_add(gate.control, gate.target) for c in circuit: c.row_add(gate.control, gate.target) return matrix.data
def __init__(self, n_qubits, **kwargs): super().__init__(n_qubits, **kwargs) self.matrix = Mat2(np.identity(n_qubits)) self.row_perm = np.arange(n_qubits) self.col_perm = np.arange(n_qubits) self.n_qubits = n_qubits
def partition2mat2(partition): return Mat2([[int(i) for i in parity] for parity in partition])
def test_matrix_multiplication(self): result = Mat2([[1, 1], [0, 0]]) self.assertEqual(self.m1 * self.m2, result) result = Mat2([[0, 1], [0, 1]]) self.assertEqual(self.m2 * self.m1, result)
def rec_steiner_gauss(matrix, architecture, full_reduce=False, x=None, y=None, permutation=None, **kwargs): """ Performs recursive Gaussian elimination that is constraint bij the given architecture :param matrix: PyZX Mat2 matrix to be reduced :param architecture: The Architecture object to conform to :param full_reduce: Whether to fully reduce or only create an upper triangular form :param x: :param y: """ #print(matrix) if permutation is None: permutation = [i for i in range(len(matrix.data))] else: matrix = Mat2([[row[i] for i in permutation] for row in matrix.data]) #print(matrix) def row_add(c0, c1): matrix.row_add(c0, c1) debug and print("Reducing", c0, c1) c0 = architecture.qubit_map[c0] c1 = architecture.qubit_map[c1] if x != None: x.row_add(c0, c1) if y != None: y.col_add(c1, c0) def steiner_reduce(col, root, nodes, usable_nodes, rec_nodes, upper): generator = steiner_reduce_column(architecture, [row[col] for row in matrix.data], root, nodes, usable_nodes, rec_nodes, upper) cnot = next(generator, None) while cnot is not None: row_add(*cnot) cnot = next(generator, None) def rec_step(cols, rows): size = len(rows) # Upper triangular form uses the same structure. p_cols = [] pivot = 0 rows2 = [r for r in range(len(matrix.data[0])) if r in rows] cols2 = [c for c in range(len(matrix.data)) if c in cols] for i, c in enumerate(cols2): if pivot < size: nodes = [ r for r in rows2[pivot:] if rows2[pivot] == r or matrix.data[r][c] == 1 ] steiner_reduce(c, rows2[pivot], nodes, cols2[i:], [], True) if matrix.data[rows2[pivot]][c] == 1: p_cols.append(c) pivot += 1 # Full reduce requires the recursion if full_reduce: pivot -= 1 for i, c in enumerate(cols): if c in p_cols: nodes = [ r for r in rows if r == rows[pivot] or matrix.data[r][c] == 1 ] usable_nodes = cols[i:] #rec_nodes = list(set([node for edge in architecture.distances["upper"][c][(max(usable_nodes),c)][1] for node in edge])) #rec_nodes = [n for n in usable_nodes if n in rec_nodes] rec_nodes = architecture.shortest_path( c, max(usable_nodes), usable_nodes) if len(nodes) > 1: #print("-", c, nodes, cols, rec_nodes) steiner_reduce(c, rows[pivot], nodes, cols, rec_nodes, False) # Do recursion on the given nodes. if len(rec_nodes) > 1: rec_step(list(reversed(rec_nodes)), rec_nodes) pivot -= 1 #return rank qubit_order = architecture.reduce_order rec_step(qubit_order, list(reversed(qubit_order)))