def _does_commute(node1, node2): """Function to verify commutation relation between two nodes in the DAG. Args: node1 (DAGnode): first node operation node2 (DAGnode): second node operation Return: bool: True if the nodes commute and false if it is not the case. """ # Create set of qubits on which the operation acts qarg1 = [node1.qargs[i] for i in range(0, len(node1.qargs))] qarg2 = [node2.qargs[i] for i in range(0, len(node2.qargs))] # Create set of cbits on which the operation acts carg1 = [node1.cargs[i] for i in range(0, len(node1.cargs))] carg2 = [node2.cargs[i] for i in range(0, len(node2.cargs))] # Commutation for classical conditional gates # if and only if the qubits are different. # TODO: qubits can be the same if conditions are identical and # the non-conditional gates commute. if node1.condition or node2.condition: intersection = set(qarg1).intersection(set(qarg2)) return not intersection # Commutation for non-unitary or parameterized or opaque ops # (e.g. measure, reset, directives or pulse gates) # if and only if the qubits and clbits are different. non_unitaries = ['measure', 'reset', 'initialize', 'delay'] def _unknown_commutator(n): return (n.op._directive or n.name in non_unitaries or n.op.is_parameterized()) if _unknown_commutator(node1) or _unknown_commutator(node2): intersection_q = set(qarg1).intersection(set(qarg2)) intersection_c = set(carg1).intersection(set(carg2)) return not (intersection_q or intersection_c) # Known non-commuting gates (TODO: add more). non_commute_gates = [{'x', 'y'}, {'x', 'z'}] if qarg1 == qarg2 and ({node1.name, node2.name} in non_commute_gates): return False # Create matrices to check commutation relation if no other criteria are matched qarg = list(set(node1.qargs + node2.qargs)) qbit_num = len(qarg) qarg1 = [qarg.index(q) for q in node1.qargs] qarg2 = [qarg.index(q) for q in node2.qargs] id_op = Operator(np.eye(2 ** qbit_num)) op12 = id_op.compose(node1.op, qargs=qarg1).compose(node2.op, qargs=qarg2) op21 = id_op.compose(node2.op, qargs=qarg2).compose(node1.op, qargs=qarg1) return op12 == op21
def check_two_qubit_weyl_decomposition(self, target_unitary, tolerance=1.e-7): """Check TwoQubitWeylDecomposition() works for a given operator""" with self.subTest(unitary=target_unitary): decomp = TwoQubitWeylDecomposition(target_unitary) op = Operator(np.eye(4)) for u, qs in ( (decomp.K2r, [0]), (decomp.K2l, [1]), (Ud(decomp.a, decomp.b, decomp.c), [0, 1]), (decomp.K1r, [0]), (decomp.K1l, [1]), ): op = op.compose(u, qs) decomp_unitary = op.data target_unitary *= la.det(target_unitary)**(-0.25) decomp_unitary *= la.det(decomp_unitary)**(-0.25) maxdists = [ np.max(np.abs(target_unitary + phase * decomp_unitary)) for phase in [1, 1j, -1, -1j] ] maxdist = np.min(maxdists) self.assertTrue( np.abs(maxdist) < tolerance, "Worst distance {}".format(maxdist))
def _commute(node1, node2): if node1.type != "op" or node2.type != "op": return False if any([ nd.name in {"barrier", "snapshot", "measure", "reset", "copy"} for nd in [node1, node2] ]): return False if node1.condition or node2.condition: return False if node1.op.is_parameterized() or node2.op.is_parameterized(): return False qarg = list(set(node1.qargs + node2.qargs)) qbit_num = len(qarg) qarg1 = [qarg.index(q) for q in node1.qargs] qarg2 = [qarg.index(q) for q in node2.qargs] id_op = Operator(np.eye(2**qbit_num)) op12 = id_op.compose(node1.op, qargs=qarg1).compose(node2.op, qargs=qarg2) op21 = id_op.compose(node2.op, qargs=qarg2).compose(node1.op, qargs=qarg1) if_commute = (op12 == op21) return if_commute
def _commute(node1, node2, cache): if node1.type != "op" or node2.type != "op": return False if any([ nd.name in {"barrier", "snapshot", "measure", "reset", "copy", "delay"} for nd in [node1, node2] ]): return False if node1.condition or node2.condition: return False if node1.op.is_parameterized() or node2.op.is_parameterized(): return False qarg = list(set(node1.qargs + node2.qargs)) qbit_num = len(qarg) qarg1 = [qarg.index(q) for q in node1.qargs] qarg2 = [qarg.index(q) for q in node2.qargs] id_op = Operator(np.eye(2**qbit_num)) node1_key = (node1.op.name, str(node1.op.params), str(qarg1)) node2_key = (node2.op.name, str(node2.op.params), str(qarg2)) if (node1_key, node2_key) in cache: op12 = cache[(node1_key, node2_key)] else: op12 = id_op.compose(node1.op, qargs=qarg1).compose(node2.op, qargs=qarg2) cache[(node1_key, node2_key)] = op12 if (node2_key, node1_key) in cache: op21 = cache[(node2_key, node1_key)] else: op21 = id_op.compose(node2.op, qargs=qarg2).compose(node1.op, qargs=qarg1) cache[(node2_key, node1_key)] = op21 if_commute = (op12 == op21) return if_commute
def _commute(node1, node2, cache): if not isinstance(node1, DAGOpNode) or not isinstance(node2, DAGOpNode): return False for nd in [node1, node2]: if nd.op._directive or nd.name in {"measure", "reset", "delay"}: return False if node1.op.condition or node2.op.condition: return False if node1.op.is_parameterized() or node2.op.is_parameterized(): return False qarg = list(set(node1.qargs + node2.qargs)) qbit_num = len(qarg) qarg1 = [qarg.index(q) for q in node1.qargs] qarg2 = [qarg.index(q) for q in node2.qargs] id_op = Operator(np.eye(2**qbit_num)) node1_key = (node1.op.name, str(node1.op.params), str(qarg1)) node2_key = (node2.op.name, str(node2.op.params), str(qarg2)) if (node1_key, node2_key) in cache: op12 = cache[(node1_key, node2_key)] else: op12 = id_op.compose(node1.op, qargs=qarg1).compose(node2.op, qargs=qarg2) cache[(node1_key, node2_key)] = op12 if (node2_key, node1_key) in cache: op21 = cache[(node2_key, node1_key)] else: op21 = id_op.compose(node2.op, qargs=qarg2).compose(node1.op, qargs=qarg1) cache[(node2_key, node1_key)] = op21 if_commute = op12 == op21 return if_commute
def _commute(node1, node2): """Function to verify commutation relation between two nodes in the DAG Args: node1 (DAGnode): first node operation (attribute ['operation'] in the DAG) node2 (DAGnode): second node operation Return: bool: True if the gates commute and false if it is not the case. """ # Create set of qubits on which the operation acts qarg1 = [node1.qargs[i].index for i in range(0, len(node1.qargs))] qarg2 = [node2.qargs[i].index for i in range(0, len(node2.qargs))] # Create set of cbits on which the operation acts carg1 = [node1.qargs[i].index for i in range(0, len(node1.cargs))] carg2 = [node2.qargs[i].index for i in range(0, len(node2.cargs))] # Commutation for classical conditional gates if node1.condition or node2.condition: intersection = set(qarg1).intersection(set(qarg2)) if intersection or carg1 or carg2: commute_condition = False else: commute_condition = True return commute_condition # Commutation for measurement if node1.name == 'measure' or node2.name == 'measure': intersection_q = set(qarg1).intersection(set(qarg2)) intersection_c = set(carg1).intersection(set(carg2)) if intersection_q or intersection_c: commute_measurement = False else: commute_measurement = True return commute_measurement # Commutation for barrier-like directives directives = ['barrier', 'snapshot'] if node1.name in directives or node2.name in directives: intersection = set(qarg1).intersection(set(qarg2)) if intersection: commute_directive = False else: commute_directive = True return commute_directive # List of non commuting gates (TO DO: add more elements) non_commute_list = [set(['x', 'y']), set(['x', 'z'])] if qarg1 == qarg2 and (set([node1.name, node2.name]) in non_commute_list): return False # Create matrices to check commutation relation if no other criteria are matched qarg = list(set(node1.qargs + node2.qargs)) qbit_num = len(qarg) qarg1 = [qarg.index(q) for q in node1.qargs] qarg2 = [qarg.index(q) for q in node2.qargs] id_op = Operator(np.eye(2**qbit_num)) op12 = id_op.compose(node1.op, qargs=qarg1).compose(node2.op, qargs=qarg2) op21 = id_op.compose(node2.op, qargs=qarg2).compose(node1.op, qargs=qarg1) if_commute = (op12 == op21) return if_commute
def _commute(node1, node2, cache): if not isinstance(node1, DAGOpNode) or not isinstance(node2, DAGOpNode): return False for nd in [node1, node2]: if nd.op._directive or nd.name in {"measure", "reset", "delay"}: return False if node1.op.condition or node2.op.condition: return False if node1.op.is_parameterized() or node2.op.is_parameterized(): return False # Assign indices to each of the qubits such that all `node1`'s qubits come first, followed by # any _additional_ qubits `node2` addresses. This helps later when we need to compose one # operator with the other, since we can easily expand `node1` with a suitable identity. qarg = {q: i for i, q in enumerate(node1.qargs)} num_qubits = len(qarg) for q in node2.qargs: if q not in qarg: qarg[q] = num_qubits num_qubits += 1 qarg1 = tuple(qarg[q] for q in node1.qargs) qarg2 = tuple(qarg[q] for q in node2.qargs) node1_key = (node1.op.name, _hashable_parameters(node1.op.params), qarg1) node2_key = (node2.op.name, _hashable_parameters(node2.op.params), qarg2) try: # We only need to try one orientation of the keys, since if we've seen the compound key # before, we've set it in both orientations. return cache[node1_key, node2_key] except KeyError: pass operator_1 = Operator(node1.op, input_dims=(2, ) * len(qarg1), output_dims=(2, ) * len(qarg1)) operator_2 = Operator(node2.op, input_dims=(2, ) * len(qarg2), output_dims=(2, ) * len(qarg2)) if qarg1 == qarg2: # Use full composition if possible to get the fastest matmul paths. op12 = operator_1.compose(operator_2) op21 = operator_2.compose(operator_1) else: # Expand operator_1 to be large enough to contain operator_2 as well; this relies on qargs1 # being the lowest possible indices so the identity can be tensored before it. extra_qarg2 = num_qubits - len(qarg1) if extra_qarg2: try: id_op = _COMMUTE_ID_OP[extra_qarg2] except KeyError: id_op = _COMMUTE_ID_OP[extra_qarg2] = Operator( np.eye(2**extra_qarg2), input_dims=(2, ) * extra_qarg2, output_dims=(2, ) * extra_qarg2, ) operator_1 = id_op.tensor(operator_1) op12 = operator_1.compose(operator_2, qargs=qarg2, front=False) op21 = operator_1.compose(operator_2, qargs=qarg2, front=True) cache[node1_key, node2_key] = cache[node2_key, node1_key] = ret = op12 == op21 return ret
def commute(self, op1: Operation, qargs1: List, cargs1: List, op2: Operation, qargs2: List, cargs2: List): """ Checks if two Operations commute. Args: op1: first operation. qargs1: first operation's qubits. cargs1: first operation's clbits. op2: second operation. qargs2: second operation's qubits. cargs2: second operation's clbits. Returns: bool: whether two operations commute. """ # We don't support commutation of conditional gates for now due to bugs in # CommutativeCancellation. See gh-8553. if getattr(op1, "condition") is not None or getattr( op2, "condition") is not None: return False # These lines are adapted from dag_dependency and say that two gates over # different quantum and classical bits necessarily commute. This is more # permissive that the check from commutation_analysis, as for example it # allows to commute X(1) and Measure(0, 0). # Presumably this check was not present in commutation_analysis as # it was only called on pairs of connected nodes from DagCircuit. intersection_q = set(qargs1).intersection(set(qargs2)) intersection_c = set(cargs1).intersection(set(cargs2)) if not (intersection_q or intersection_c): return True # These lines are adapted from commutation_analysis, which is more restrictive than the # check from dag_dependency when considering nodes with "_directive". It would be nice to # think which optimizations from dag_dependency can indeed be used. for op in [op1, op2]: if (getattr(op, "_directive", False) or op.name in {"measure", "reset", "delay"} or op.is_parameterized()): return False # The main code is adapted from commutative analysis. # Assign indices to each of the qubits such that all `node1`'s qubits come first, followed by # any _additional_ qubits `node2` addresses. This helps later when we need to compose one # operator with the other, since we can easily expand `node1` with a suitable identity. qarg = {q: i for i, q in enumerate(qargs1)} num_qubits = len(qarg) for q in qargs2: if q not in qarg: qarg[q] = num_qubits num_qubits += 1 qarg1 = tuple(qarg[q] for q in qargs1) qarg2 = tuple(qarg[q] for q in qargs2) node1_key = (op1.name, self._hashable_parameters(op1.params), qarg1) node2_key = (op2.name, self._hashable_parameters(op2.params), qarg2) try: # We only need to try one orientation of the keys, since if we've seen the compound key # before, we've set it in both orientations. return self.cache[node1_key, node2_key] except KeyError: pass operator_1 = Operator(op1, input_dims=(2, ) * len(qarg1), output_dims=(2, ) * len(qarg1)) operator_2 = Operator(op2, input_dims=(2, ) * len(qarg2), output_dims=(2, ) * len(qarg2)) if qarg1 == qarg2: # Use full composition if possible to get the fastest matmul paths. op12 = operator_1.compose(operator_2) op21 = operator_2.compose(operator_1) else: # Expand operator_1 to be large enough to contain operator_2 as well; this relies on qargs1 # being the lowest possible indices so the identity can be tensored before it. extra_qarg2 = num_qubits - len(qarg1) if extra_qarg2: id_op = _identity_op(2**extra_qarg2) operator_1 = id_op.tensor(operator_1) op12 = operator_1.compose(operator_2, qargs=qarg2, front=False) op21 = operator_1.compose(operator_2, qargs=qarg2, front=True) self.cache[node1_key, node2_key] = self.cache[node2_key, node1_key] = ret = op12 == op21 return ret