def test_clifford_circuit_noise(self, method, device): """Test simulation with mixed Clifford quantum errors in circuit.""" backend = self.backend(method=method, device=device) shots = 1000 error1 = noise.QuantumError([ ([(IGate(), [0])], 0.8), ([(Reset(), [0])], 0.1), ([(HGate(), [0])], 0.1)]) error2 = noise.QuantumError([ ([(IGate(), [0])], 0.75), ([(Reset(), [0])], 0.1), ([(Reset(), [1])], 0.1), ([(Reset(), [0]), (Reset(), [1])], 0.05)]) qc = QuantumCircuit(2) qc.h(0) qc.append(error1, [0]) qc.cx(0, 1) qc.append(error2, [0, 1]) target_probs = qi.DensityMatrix(qc).probabilities_dict() # Add measurement qc.measure_all() result = backend.run(qc, shots=shots).result() self.assertSuccess(result) probs = {key: val / shots for key, val in result.get_counts(0).items()} self.assertDictAlmostEqual(target_probs, probs, delta=0.1)
def to_instruction(self): """Convert to Pauli circuit instruction.""" from math import pi pauli, phase = self._to_label(self.z, self.x, self._phase[0], full_group=False, return_phase=True) if len(pauli) == 1: gate = { "I": IGate(), "X": XGate(), "Y": YGate(), "Z": ZGate() }[pauli] else: gate = PauliGate(pauli) if not phase: return gate # Add global phase circuit = QuantumCircuit(self.num_qubits, name=str(self)) circuit.global_phase = -phase * pi / 2 circuit.append(gate, range(self.num_qubits)) return circuit.to_instruction()
def reset_error(prob0, prob1=0): r""" Return a single qubit reset quantum error channel. The error channel returned is given by the map .. math:: E(ρ) = (1 - p_0 - p_1) ρ + \text{Tr}[ρ] \left( p_0 |0 \rangle\langle 0| + p_1 |1 \rangle\langle 1| \right) where the probability of no reset is given by :math:`1 - p_0 - p_1`. Args: prob0 (double): reset probability to :math:`|0\rangle`. prob1 (double): reset probability to :math:`|1\rangle`. Returns: QuantumError: the quantum error object. Raises: NoiseError: If noise parameters are invalid. """ if prob0 < 0 or prob1 < 0 or prob0 > 1 or prob1 > 1 or (prob0 + prob1) > 1: raise NoiseError("Invalid reset probabilities.") noise_ops = [([(IGate(), [0])], 1 - prob0 - prob1), ([(Reset(), [0])], prob0), ([(Reset(), [0]), (XGate(), [0])], prob1)] return QuantumError(noise_ops)
def from_label(cls, label): """Return a tensor product of single-qubit operators. Args: label (string): single-qubit operator string. Returns: Operator: The N-qubit operator. Raises: QiskitError: if the label contains invalid characters, or the length of the label is larger than an explicitly specified num_qubits. Additional Information: The labels correspond to the single-qubit matrices: 'I': [[1, 0], [0, 1]] 'X': [[0, 1], [1, 0]] 'Y': [[0, -1j], [1j, 0]] 'Z': [[1, 0], [0, -1]] 'H': [[1, 1], [1, -1]] / sqrt(2) 'S': [[1, 0], [0 , 1j]] 'T': [[1, 0], [0, (1+1j) / sqrt(2)]] '0': [[1, 0], [0, 0]] '1': [[0, 0], [0, 1]] '+': [[0.5, 0.5], [0.5 , 0.5]] '-': [[0.5, -0.5], [-0.5 , 0.5]] 'r': [[0.5, -0.5j], [0.5j , 0.5]] 'l': [[0.5, 0.5j], [-0.5j , 0.5]] """ # Check label is valid label_mats = { 'I': IGate().to_matrix(), 'X': XGate().to_matrix(), 'Y': YGate().to_matrix(), 'Z': ZGate().to_matrix(), 'H': HGate().to_matrix(), 'S': SGate().to_matrix(), 'T': TGate().to_matrix(), '0': np.array([[1, 0], [0, 0]], dtype=complex), '1': np.array([[0, 0], [0, 1]], dtype=complex), '+': np.array([[0.5, 0.5], [0.5, 0.5]], dtype=complex), '-': np.array([[0.5, -0.5], [-0.5, 0.5]], dtype=complex), 'r': np.array([[0.5, -0.5j], [0.5j, 0.5]], dtype=complex), 'l': np.array([[0.5, 0.5j], [-0.5j, 0.5]], dtype=complex), } if re.match(r'^[IXYZHST01rl\-+]+$', label) is None: raise QiskitError('Label contains invalid characters.') # Initialize an identity matrix and apply each gate num_qubits = len(label) op = Operator(np.eye(2 ** num_qubits, dtype=complex)) for qubit, char in enumerate(reversed(label)): if char != 'I': op = op.compose(label_mats[char], qargs=[qubit]) return op
def from_label(label): """Return a tensor product of single-qubit Clifford gates. Args: label (string): single-qubit operator string. Returns: Clifford: The N-qubit Clifford operator. Raises: QiskitError: if the label contains invalid characters. Additional Information: The labels correspond to the single-qubit Cliffords are * - Label - Stabilizer - Destabilizer * - ``"I"`` - +Z - +X * - ``"X"`` - -Z - +X * - ``"Y"`` - -Z - -X * - ``"Z"`` - +Z - -X * - ``"H"`` - +X - +Z * - ``"S"`` - +Z - +Y """ # Check label is valid label_gates = { 'I': IGate(), 'X': XGate(), 'Y': YGate(), 'Z': ZGate(), 'H': HGate(), 'S': SGate() } if re.match(r'^[IXYZHS\-+]+$', label) is None: raise QiskitError('Label contains invalid characters.') # Initialize an identity matrix and apply each gate num_qubits = len(label) op = Clifford(np.eye(2 * num_qubits, dtype=np.bool)) for qubit, char in enumerate(reversed(label)): _append_circuit(op, label_gates[char], qargs=[qubit]) return op
def test_pauli_error_1q_gate_from_string(self): """Test single-qubit pauli error as gate qobj from string label""" paulis = ['I', 'X', 'Y', 'Z'] probs = [0.4, 0.3, 0.2, 0.1] actual = pauli_error(zip(paulis, probs)) expected = QuantumError([(IGate(), 0.4), (XGate(), 0.3), (YGate(), 0.2), (ZGate(), 0.1)]) for i in range(actual.size): circ, prob = actual.error_term(i) expected_circ, expected_prob = expected.error_term(i) self.assertEqual(circ, expected_circ, msg=f"Incorrect {i}-th circuit") self.assertAlmostEqual(prob, expected_prob, msg=f"Incorrect {i}-th probability")
def to_instruction(self): """Convert to Pauli circuit instruction.""" from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.circuit.library.standard_gates import IGate, XGate, YGate, ZGate gates = {'I': IGate(), 'X': XGate(), 'Y': YGate(), 'Z': ZGate()} label = self.to_label() num_qubits = self.num_qubits qreg = QuantumRegister(num_qubits) circuit = QuantumCircuit(qreg, name='Pauli:{}'.format(label)) for i, pauli in enumerate(reversed(label)): circuit.append(gates[pauli], [qreg[i]]) return circuit.to_instruction()
def test_thermal_relaxation_error_t1_equal_t2_1state(self): """Test qobj instructions return for t1=t2""" actual = thermal_relaxation_error(1, 1, 1, 1) expected = QuantumError([ (IGate(), np.exp(-1)), ([(Reset(), [0]), (XGate(), [0])], 1 - np.exp(-1)), ]) for i in range(actual.size): circ, prob = actual.error_term(i) expected_circ, expected_prob = expected.error_term(i) self.assertEqual(circ, expected_circ, msg=f"Incorrect {i}-th circuit") self.assertAlmostEqual(prob, expected_prob, msg=f"Incorrect {i}-th probability")
def test_depolarizing_error_1q_gate(self): """Test 1-qubit depolarizing error as gate qobj""" p_depol = 0.3 actual = depolarizing_error(p_depol, 1) expected = QuantumError([ (IGate(), 1 - p_depol*3/4), (XGate(), p_depol/4), (YGate(), p_depol/4), (ZGate(), p_depol/4) ]) for i in range(actual.size): circ, prob = actual.error_term(i) expected_circ, expected_prob = expected.error_term(i) self.assertEqual(circ, expected_circ, msg=f"Incorrect {i}-th circuit") self.assertAlmostEqual(prob, expected_prob, msg=f"Incorrect {i}-th probability")
def test_from_dict(self): noise_ops_1q = [((IGate(), [0]), 0.9), ((XGate(), [0]), 0.1)] noise_ops_2q = [((PauliGate('II'), [0, 1]), 0.9), ((PauliGate('IX'), [0, 1]), 0.045), ((PauliGate('XI'), [0, 1]), 0.045), ((PauliGate('XX'), [0, 1]), 0.01)] noise_model = NoiseModel() with self.assertWarns(DeprecationWarning): noise_model.add_quantum_error(QuantumError(noise_ops_1q, 1), 'h', [0]) noise_model.add_quantum_error(QuantumError(noise_ops_1q, 1), 'h', [1]) noise_model.add_quantum_error(QuantumError(noise_ops_2q, 2), 'cx', [0, 1]) noise_model.add_quantum_error(QuantumError(noise_ops_2q, 2), 'cx', [1, 0]) deserialized = NoiseModel.from_dict(noise_model.to_dict()) self.assertEqual(noise_model, deserialized)
def test_auto_method_clifford_circuits_and_reset_noise(self): """Test statevector method is used for Clifford circuit""" # Test noise model noise_circs = [Reset(), IGate()] noise_probs = [0.5, 0.5] error = QuantumError(zip(noise_circs, noise_probs)) noise_model = NoiseModel() noise_model.add_all_qubit_quantum_error( error, ['id', 'x', 'y', 'z', 'h', 's', 'sdg']) backend = self.backend(noise_model=noise_model) # Test circuits shots = 100 circuits = ref_2q_clifford.cz_gate_circuits_deterministic( final_measure=True) result = backend.run(circuits, shots=shots).result() success = getattr(result, 'success', False) self.assertTrue(success) self.compare_result_metadata(result, circuits, 'method', 'stabilizer')
def test_thermal_relaxation_error_gate(self): """Test qobj instructions return for t2 < t1""" t1, t2, time, p1 = (2, 1, 1, 0.3) actual = thermal_relaxation_error(t1, t2, time, p1) p_z = 0.5 * np.exp(-1 / t1) * (1 - np.exp(-(1 / t2 - 1 / t1) * time)) p_reset0 = (1 - p1) * (1 - np.exp(-1 / t1)) p_reset1 = p1 * (1 - np.exp(-1 / t1)) expected = QuantumError([ (IGate(), 1 - p_z - p_reset0 - p_reset1), (ZGate(), p_z), (Reset(), p_reset0), ([(Reset(), [0]), (XGate(), [0])], p_reset1), ]) for i in range(actual.size): circ, prob = actual.error_term(i) expected_circ, expected_prob = expected.error_term(i) self.assertEqual(circ, expected_circ, msg=f"Incorrect {i}-th circuit") self.assertAlmostEqual(prob, expected_prob, msg=f"Incorrect {i}-th probability")
def test_reset_2_qubit(self): # approximating amplitude damping using relaxation operators gamma = 0.23 p = (gamma - numpy.sqrt(1 - gamma) + 1) / 2 A0 = [[1, 0], [0, numpy.sqrt(1 - gamma)]] A1 = [[0, numpy.sqrt(gamma)], [0, 0]] error_1 = QuantumError([([(Kraus([A0, A1]), [0]), (IGate(), [1])], 1)]) error_2 = QuantumError([([(Kraus([A0, A1]), [1]), (IGate(), [0])], 1)]) expected_results_1 = QuantumError([([(IGate(), [0]), (IGate(), [1])], 1 - p), ([(Reset(), [0]), (IGate(), [1])], p)]) expected_results_2 = QuantumError([([(IGate(), [1]), (IGate(), [0])], 1 - p), ([(Reset(), [1]), (IGate(), [0])], p)]) results_1 = approximate_quantum_error(error_1, operator_string="reset") results_2 = approximate_quantum_error(error_2, operator_string="reset") self.assertErrorsAlmostEqual(results_1, expected_results_1) self.assertErrorsAlmostEqual(results_2, expected_results_2)
if a ``dict`` is given, ``list`` is overwritten. The ``string`` supports only 1- or 2-qubit errors and its possible values are ``'pauli'``, ``'reset'``, ``'clifford'``. The ``'clifford'`` does not support 2-qubit errors. """ def approximate(noise): return approximate_quantum_error(noise, operator_string=operator_string, operator_dict=operator_dict, operator_list=operator_list) return transform_noise_model(model, approximate) # pauli operators _PAULIS_Q0 = [[(IGate(), [0])], [(XGate(), [0])], [(YGate(), [0])], [(ZGate(), [0])]] _PAULIS_Q1 = [[(IGate(), [1])], [(XGate(), [1])], [(YGate(), [1])], [(ZGate(), [1])]] _PAULIS_Q0Q1 = [op_q0 + op_q1 for op_q0 in _PAULIS_Q0 for op_q1 in _PAULIS_Q1] # reset operators _RESET_Q0_TO_0 = [(Reset(), [0])] _RESET_Q0_TO_1 = [(Reset(), [0]), (XGate(), [0])] _RESET_Q0 = [[(IGate(), [0])], _RESET_Q0_TO_0, _RESET_Q0_TO_1] _RESET_Q1_TO_0 = [(Reset(), [1])] _RESET_Q1_TO_1 = [(Reset(), [1]), (XGate(), [1])] _RESET_Q1 = [[(IGate(), [1])], _RESET_Q1_TO_0, _RESET_Q1_TO_1] _RESET_Q0Q1 = [op_q0 + op_q1 for op_q0 in _RESET_Q0 for op_q1 in _RESET_Q1] # preset operator table _PRESET_OPERATOR_TABLE = { "pauli": {
def thermal_relaxation_error(t1, t2, time, excited_state_population=0): r""" Return a single-qubit thermal relaxation quantum error channel. Args: t1 (double): the :math:`T_1` relaxation time constant. t2 (double): the :math:`T_2` relaxation time constant. time (double): the gate time for relaxation error. excited_state_population (double): the population of :math:`|1\rangle` state at equilibrium (default: 0). Returns: QuantumError: a quantum error object for a noise model. Raises: NoiseError: If noise parameters are invalid. Additional information: * For parameters to be valid :math:`T_1` and :math:`T_2` must satisfy :math:`T_2 \le 2 T_1`. * If :math:`T_2 \le T_1` the error can be expressed as a mixed reset and unitary error channel. * If :math:`T_1 < T_2 \le 2 T_1` the error must be expressed as a general non-unitary Kraus error channel. """ if excited_state_population < 0: raise NoiseError("Invalid excited state population " "({} < 0).".format(excited_state_population)) if excited_state_population > 1: raise NoiseError("Invalid excited state population " "({} > 1).".format(excited_state_population)) if time < 0: raise NoiseError("Invalid gate_time ({} < 0)".format(time)) if t1 <= 0: raise NoiseError("Invalid T_1 relaxation time parameter: T_1 <= 0.") if t2 <= 0: raise NoiseError("Invalid T_2 relaxation time parameter: T_2 <= 0.") if t2 - 2 * t1 > 0: raise NoiseError( "Invalid T_2 relaxation time parameter: T_2 greater than 2 * T_1.") # T1 relaxation rate if t1 == np.inf: rate1 = 0 p_reset = 0 else: rate1 = 1 / t1 p_reset = 1 - np.exp(-time * rate1) # T2 dephasing rate if t2 == np.inf: rate2 = 0 exp_t2 = 1 else: rate2 = 1 / t2 exp_t2 = np.exp(-time * rate2) # Qubit state equilibrium probabilities p0 = 1 - excited_state_population p1 = excited_state_population if t2 > t1: # If T_2 > T_1 we must express this as a Kraus channel # We start with the Choi-matrix representation: chan = Choi( np.array([[1 - p1 * p_reset, 0, 0, exp_t2], [0, p1 * p_reset, 0, 0], [0, 0, p0 * p_reset, 0], [exp_t2, 0, 0, 1 - p0 * p_reset]])) return QuantumError(Kraus(chan)) else: # If T_2 < T_1 we can express this channel as a probabilistic # mixture of reset operations and unitary errors: circuits = [[(IGate(), [0])], [(ZGate(), [0])], [(Reset(), [0])], [(Reset(), [0]), (XGate(), [0])]] # Probability p_reset0 = p_reset * p0 p_reset1 = p_reset * p1 p_z = (1 - p_reset) * (1 - np.exp(-time * (rate2 - rate1))) / 2 p_identity = 1 - p_z - p_reset0 - p_reset1 probabilities = [p_identity, p_z, p_reset0, p_reset1] return QuantumError(zip(circuits, probabilities))
def _pre_runhook(self, dag: DAGCircuit): super()._pre_runhook(dag) num_pulses = len(self._dd_sequence) # Check if physical circuit is given if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: raise TranspilerError("DD runs on physical circuits only.") # Set default spacing otherwise validate user input if self._spacing is None: mid = 1 / num_pulses end = mid / 2 self._spacing = [end] + [mid] * (num_pulses - 1) + [end] else: if sum(self._spacing) != 1 or any(a < 0 for a in self._spacing): raise TranspilerError( "The spacings must be given in terms of fractions " "of the slack period and sum to 1.") # Check if DD sequence is identity if num_pulses != 1: if num_pulses % 2 != 0: raise TranspilerError( "DD sequence must contain an even number of gates (or 1).") noop = np.eye(2) for gate in self._dd_sequence: noop = noop.dot(gate.to_matrix()) if not matrix_equal(noop, IGate().to_matrix(), ignore_phase=True): raise TranspilerError( "The DD sequence does not make an identity operation.") self._sequence_phase = np.angle(noop[0][0]) # Precompute qubit-wise DD sequence length for performance for qubit in dag.qubits: physical_index = dag.qubits.index(qubit) if self._qubits and physical_index not in self._qubits: continue sequence_lengths = [] for gate in self._dd_sequence: try: # Check calibration. gate_length = dag.calibrations[gate.name][(physical_index, gate.params)] if gate_length % self._alignment != 0: # This is necessary to implement lightweight scheduling logic for this pass. # Usually the pulse alignment constraint and pulse data chunk size take # the same value, however, we can intentionally violate this pattern # at the gate level. For example, we can create a schedule consisting of # a pi-pulse of 32 dt followed by a post buffer, i.e. delay, of 4 dt # on the device with 16 dt constraint. Note that the pi-pulse length # is multiple of 16 dt but the gate length of 36 is not multiple of it. # Such pulse gate should be excluded. raise TranspilerError( f"Pulse gate {gate.name} with length non-multiple of {self._alignment} " f"is not acceptable in {self.__class__.__name__} pass." ) except KeyError: gate_length = self._durations.get(gate, physical_index) sequence_lengths.append(gate_length) # Update gate duration. This is necessary for current timeline drawer, i.e. scheduled. gate.duration = gate_length self._dd_sequence_lengths[qubit] = sequence_lengths
def _gate_gradient_dict( gate: Gate) -> List[Tuple[List[complex], List[Instruction]]]: r"""Given a parameterized gate U(theta) with derivative dU(theta)/dtheta = sum_ia_iU(theta)V_i. This function returns a:=[a_0, ...] and V=[V_0, ...] Suppose U takes multiple parameters, i.e., U(theta^0, ... theta^k). The returned coefficients and gates are ordered accordingly. Only parameterized Qiskit gates are supported. Args: gate: The gate for which the derivative is being computed. Returns: The coefficients and the gates used for the metric computation for each parameter of the respective gates. [([a^0], [V^0]) ..., ([a^k], [V^k])] Raises: AquaError: If the input gate is controlled by another state but '|1>^{\otimes k}' TypeError: If the input gate is not a supported parametrized gate. """ # pylint: disable=too-many-return-statements if isinstance(gate, PhaseGate): # theta return [([0.5j, -0.5j], [IGate(), CZGate()])] if isinstance(gate, UGate): # theta, lambda, phi return [([-0.5j], [CZGate()]), ([-0.5j], [CZGate()]), ([-0.5j], [CZGate()])] if isinstance(gate, RXGate): # theta return [([-0.5j], [CXGate()])] if isinstance(gate, RYGate): # theta return [([-0.5j], [CYGate()])] if isinstance(gate, RZGate): # theta return [([-0.5j], [CZGate()])] if isinstance(gate, RXXGate): # theta cxx_circ = QuantumCircuit(3) cxx_circ.cx(0, 1) cxx_circ.cx(0, 2) cxx = cxx_circ.to_instruction() return [([-0.5j], [cxx])] if isinstance(gate, RYYGate): # theta cyy_circ = QuantumCircuit(3) cyy_circ.cy(0, 1) cyy_circ.cy(0, 2) cyy = cyy_circ.to_instruction() return [([-0.5j], [cyy])] if isinstance(gate, RZZGate): # theta czz_circ = QuantumCircuit(3) czz_circ.cz(0, 1) czz_circ.cz(0, 2) czz = czz_circ.to_instruction() return [([-0.5j], [czz])] if isinstance(gate, RZXGate): # theta czx_circ = QuantumCircuit(3) czx_circ.cx(0, 2) czx_circ.cz(0, 1) czx = czx_circ.to_instruction() return [([-0.5j], [czx])] if isinstance(gate, ControlledGate): # TODO support arbitrary control states if gate.ctrl_state != 2**gate.num_ctrl_qubits - 1: raise AquaError( 'Function only support controlled gates with control state `1` on all control ' 'qubits.') base_coeffs_gates = LinComb._gate_gradient_dict(gate.base_gate) coeffs_gates = [] # The projectors needed for the gradient of a controlled gate are integrated by a sum # of gates. # The following line generates the decomposition gates. proj_gates_controlled = [[ (-1)**p.count(ZGate()), p ] for p in product([IGate(), ZGate()], repeat=gate.num_ctrl_qubits) ] for base_coeffs, base_gates in base_coeffs_gates: # loop over parameters coeffs = [] gates = [] for phase, proj_gates in proj_gates_controlled: coeffs.extend([ phase * c / (2**gate.num_ctrl_qubits) for c in base_coeffs ]) for base_gate in base_gates: controlled_circ = QuantumCircuit(gate.num_ctrl_qubits + gate.num_qubits) for i, proj_gate in enumerate(proj_gates): if isinstance(proj_gate, ZGate): controlled_circ.cz(0, i + 1) if not isinstance(base_gate, IGate): controlled_circ.append(base_gate, [ 0, range(gate.num_ctrl_qubits + 1, gate.num_ctrl_qubits + gate.num_qubits) ]) gates.append(controlled_circ.to_instruction()) c_g = (coeffs, gates) coeffs_gates.append(c_g) return coeffs_gates raise TypeError('Unrecognized parametrized gate, {}'.format(gate))
def run(self, dag): """Run the DynamicalDecoupling pass on dag. Args: dag (DAGCircuit): a scheduled DAG. Returns: DAGCircuit: equivalent circuit with delays interrupted by DD, where possible. Raises: TranspilerError: if the circuit is not mapped on physical qubits. """ if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: raise TranspilerError("DD runs on physical circuits only.") if dag.duration is None: raise TranspilerError("DD runs after circuit is scheduled.") num_pulses = len(self._dd_sequence) sequence_gphase = 0 if num_pulses != 1: if num_pulses % 2 != 0: raise TranspilerError( "DD sequence must contain an even number of gates (or 1).") noop = np.eye(2) for gate in self._dd_sequence: noop = noop.dot(gate.to_matrix()) if not matrix_equal(noop, IGate().to_matrix(), ignore_phase=True): raise TranspilerError( "The DD sequence does not make an identity operation.") sequence_gphase = np.angle(noop[0][0]) if self._qubits is None: self._qubits = set(range(dag.num_qubits())) else: self._qubits = set(self._qubits) if self._spacing: if sum(self._spacing) != 1 or any(a < 0 for a in self._spacing): raise TranspilerError( "The spacings must be given in terms of fractions " "of the slack period and sum to 1.") else: # default to balanced spacing mid = 1 / num_pulses end = mid / 2 self._spacing = [end] + [mid] * (num_pulses - 1) + [end] new_dag = dag._copy_circuit_metadata() qubit_index_map = { qubit: index for index, qubit in enumerate(new_dag.qubits) } index_sequence_duration_map = {} for qubit in new_dag.qubits: physical_qubit = qubit_index_map[qubit] dd_sequence_duration = 0 for gate in self._dd_sequence: gate.duration = self._durations.get(gate, physical_qubit) dd_sequence_duration += gate.duration index_sequence_duration_map[physical_qubit] = dd_sequence_duration for nd in dag.topological_op_nodes(): if not isinstance(nd.op, Delay): new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) continue dag_qubit = nd.qargs[0] physical_qubit = qubit_index_map[dag_qubit] if physical_qubit not in self._qubits: # skip unwanted qubits new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) continue pred = next(dag.predecessors(nd)) succ = next(dag.successors(nd)) if self._skip_reset_qubits: # discount initial delays if pred.type == "in" or isinstance(pred.op, Reset): new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) continue dd_sequence_duration = index_sequence_duration_map[physical_qubit] slack = nd.op.duration - dd_sequence_duration if slack <= 0: # dd doesn't fit new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) continue if num_pulses == 1: # special case of using a single gate for DD u_inv = self._dd_sequence[0].inverse().to_matrix() theta, phi, lam, phase = OneQubitEulerDecomposer( ).angles_and_phase(u_inv) # absorb the inverse into the successor (from left in circuit) if succ.type == "op" and isinstance(succ.op, (UGate, U3Gate)): theta_r, phi_r, lam_r = succ.op.params succ.op.params = Optimize1qGates.compose_u3( theta_r, phi_r, lam_r, theta, phi, lam) sequence_gphase += phase # absorb the inverse into the predecessor (from right in circuit) elif pred.type == "op" and isinstance(pred.op, (UGate, U3Gate)): theta_l, phi_l, lam_l = pred.op.params pred.op.params = Optimize1qGates.compose_u3( theta, phi, lam, theta_l, phi_l, lam_l) sequence_gphase += phase # don't do anything if there's no single-qubit gate to absorb the inverse else: new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) continue # insert the actual DD sequence taus = [int(slack * a) for a in self._spacing] unused_slack = slack - sum( taus) # unused, due to rounding to int multiples of dt middle_index = int( (len(taus) - 1) / 2) # arbitrary: redistribute to middle taus[ middle_index] += unused_slack # now we add up to original delay duration for tau, gate in itertools.zip_longest(taus, self._dd_sequence): if tau > 0: new_dag.apply_operation_back(Delay(tau), [dag_qubit]) if gate is not None: new_dag.apply_operation_back(gate, [dag_qubit]) new_dag.global_phase = _mod_2pi(new_dag.global_phase + sequence_gphase) return new_dag
def mixed_unitary_error(noise_ops, standard_gates=None): """ Return a mixed unitary quantum error channel. The input should be a list of pairs ``(U[j], p[j])``, where ``U[j]`` is a unitary matrix and ``p[j]`` is a probability. All probabilities must sum to 1 for the input ops to be valid. Args: noise_ops (list[pair[matrix, double]]): unitary error matrices. standard_gates (bool): DEPRECATED, Check if input matrices are standard gates. Returns: QuantumError: The quantum error object. Raises: NoiseError: if error parameters are invalid. """ if standard_gates is not None: warnings.warn( '"standard_gates" option has been deprecated as of qiskit-aer 0.10.0' ' and will be removed no earlier than 3 months from that release date.' ' Use directly init e.g. QuantumError([(IGate(), prob1), (ZGate(), prob2)]) instead.', DeprecationWarning, stacklevel=2) # Error checking if not isinstance(noise_ops, (list, tuple, zip)): raise NoiseError("Input noise ops is not a list.") # Convert to numpy arrays noise_ops = [(np.array(op, dtype=complex), p) for op, p in noise_ops] if not noise_ops: raise NoiseError("Input noise list is empty.") # Check for identity unitaries prob_identity = 0. instructions = [] instructions_probs = [] num_qubits = int(np.log2(noise_ops[0][0].shape[0])) if noise_ops[0][0].shape != (2**num_qubits, 2**num_qubits): raise NoiseError( "A unitary matrix in input noise_ops is not a multi-qubit matrix.") for unitary, prob in noise_ops: # Check unitary if unitary.shape != noise_ops[0][0].shape: raise NoiseError("Input matrices different size.") if not is_unitary_matrix(unitary): raise NoiseError("Input matrix is not unitary.") if is_identity_matrix(unitary): prob_identity += prob else: if standard_gates: # TODO: to be removed after deprecation period qubits = list(range(num_qubits)) instr = _make_unitary_instruction( unitary, qubits, standard_gates=standard_gates) else: instr = UnitaryGate(unitary) instructions.append(instr) instructions_probs.append(prob) if prob_identity > 0: if standard_gates: # TODO: to be removed after deprecation period instructions.append([{"name": "id", "qubits": [0]}]) else: instructions.append(IGate()) instructions_probs.append(prob_identity) return QuantumError(zip(instructions, instructions_probs))
def convert_to_target(conf_dict: dict, props_dict: dict = None, defs_dict: dict = None) -> Target: """Uses configuration, properties and pulse defaults dicts to construct and return Target class. """ name_mapping = { "id": IGate(), "sx": SXGate(), "x": XGate(), "cx": CXGate(), "rz": RZGate(Parameter("λ")), "reset": Reset(), } custom_gates = {} qubit_props = None if props_dict: qubit_props = qubit_props_from_props(props_dict) target = Target(qubit_properties=qubit_props) # Parse from properties if it exsits if props_dict is not None: # Parse instructions gates = {} for gate in props_dict["gates"]: name = gate["gate"] if name in name_mapping: if name not in gates: gates[name] = {} elif name not in custom_gates: custom_gate = Gate(name, len(gate["qubits"]), []) custom_gates[name] = custom_gate gates[name] = {} qubits = tuple(gate["qubits"]) gate_props = {} for param in gate["parameters"]: if param["name"] == "gate_error": gate_props["error"] = param["value"] if param["name"] == "gate_length": gate_props["duration"] = apply_prefix( param["value"], param["unit"]) gates[name][qubits] = InstructionProperties(**gate_props) for gate, props in gates.items(): if gate in name_mapping: inst = name_mapping.get(gate) else: inst = custom_gates[gate] target.add_instruction(inst, props) # Create measurement instructions: measure_props = {} count = 0 for qubit in props_dict["qubits"]: qubit_prop = {} for prop in qubit: if prop["name"] == "readout_length": qubit_prop["duration"] = apply_prefix( prop["value"], prop["unit"]) if prop["name"] == "readout_error": qubit_prop["error"] = prop["value"] measure_props[(count, )] = InstructionProperties(**qubit_prop) count += 1 target.add_instruction(Measure(), measure_props) # Parse from configuration because properties doesn't exist else: for gate in conf_dict["gates"]: name = gate["name"] gate_props = {tuple(x): None for x in gate["coupling_map"]} if name in name_mapping: target.add_instruction(name_mapping[name], gate_props) else: custom_gate = Gate(name, len(gate["coupling_map"][0]), []) target.add_instruction(custom_gate, gate_props) measure_props = {(n, ): None for n in range(conf_dict["n_qubits"])} target.add_instruction(Measure(), measure_props) # parse global configuration properties dt = conf_dict.get("dt") if dt: target.dt = dt * 1e-9 if "timing_constraints" in conf_dict: target.granularity = conf_dict["timing_constraints"].get("granularity") target.min_length = conf_dict["timing_constraints"].get("min_length") target.pulse_alignment = conf_dict["timing_constraints"].get( "pulse_alignment") target.aquire_alignment = conf_dict["timing_constraints"].get( "acquire_alignment") # If pulse defaults exists use that as the source of truth if defs_dict is not None: # TODO remove the usage of PulseDefaults as it will be deprecated in the future pulse_defs = PulseDefaults.from_dict(defs_dict) inst_map = pulse_defs.instruction_schedule_map for inst in inst_map.instructions: for qarg in inst_map.qubits_with_instruction(inst): sched = inst_map.get(inst, qarg) if inst in target: try: qarg = tuple(qarg) except TypeError: qarg = (qarg, ) if inst == "measure": for qubit in qarg: target[inst][(qubit, )].calibration = sched else: target[inst][qarg].calibration = sched target.add_instruction(Delay(Parameter("t")), {(bit, ): None for bit in range(target.num_qubits)}) return target
def convert_to_target( configuration: BackendConfiguration, properties: BackendProperties = None, defaults: PulseDefaults = None, ) -> Target: """Uses configuration, properties and pulse defaults to construct and return Target class. """ name_mapping = { "id": IGate(), "sx": SXGate(), "x": XGate(), "cx": CXGate(), "rz": RZGate(Parameter("λ")), "reset": Reset(), } custom_gates = {} target = None # Parse from properties if it exsits if properties is not None: qubit_properties = qubit_props_list_from_props(properties=properties) target = Target( num_qubits=configuration.n_qubits, qubit_properties=qubit_properties ) # Parse instructions gates: Dict[str, Any] = {} for gate in properties.gates: name = gate.gate if name in name_mapping: if name not in gates: gates[name] = {} elif name not in custom_gates: custom_gate = Gate(name, len(gate.qubits), []) custom_gates[name] = custom_gate gates[name] = {} qubits = tuple(gate.qubits) gate_props = {} for param in gate.parameters: if param.name == "gate_error": gate_props["error"] = param.value if param.name == "gate_length": gate_props["duration"] = apply_prefix(param.value, param.unit) gates[name][qubits] = InstructionProperties(**gate_props) for gate, props in gates.items(): if gate in name_mapping: inst = name_mapping.get(gate) else: inst = custom_gates[gate] target.add_instruction(inst, props) # Create measurement instructions: measure_props = {} for qubit, _ in enumerate(properties.qubits): measure_props[(qubit,)] = InstructionProperties( duration=properties.readout_length(qubit), error=properties.readout_error(qubit), ) target.add_instruction(Measure(), measure_props) # Parse from configuration because properties doesn't exist else: target = Target(num_qubits=configuration.n_qubits) for gate in configuration.gates: name = gate.name gate_props = ( {tuple(x): None for x in gate.coupling_map} # type: ignore[misc] if hasattr(gate, "coupling_map") else {None: None} ) gate_len = len(gate.coupling_map[0]) if hasattr(gate, "coupling_map") else 0 if name in name_mapping: target.add_instruction(name_mapping[name], gate_props) else: custom_gate = Gate(name, gate_len, []) target.add_instruction(custom_gate, gate_props) target.add_instruction(Measure()) # parse global configuration properties if hasattr(configuration, "dt"): target.dt = configuration.dt if hasattr(configuration, "timing_constraints"): target.granularity = configuration.timing_constraints.get("granularity") target.min_length = configuration.timing_constraints.get("min_length") target.pulse_alignment = configuration.timing_constraints.get("pulse_alignment") target.aquire_alignment = configuration.timing_constraints.get( "acquire_alignment" ) # If a pulse defaults exists use that as the source of truth if defaults is not None: inst_map = defaults.instruction_schedule_map for inst in inst_map.instructions: for qarg in inst_map.qubits_with_instruction(inst): sched = inst_map.get(inst, qarg) if inst in target: try: qarg = tuple(qarg) except TypeError: qarg = (qarg,) if inst == "measure": for qubit in qarg: target[inst][(qubit,)].calibration = sched else: target[inst][qarg].calibration = sched if "delay" not in target: target.add_instruction( Delay(Parameter("t")), {(bit,): None for bit in range(target.num_qubits)} ) return target
('z', 'h', 'sdg'), ('s', 'h', 'z'), ('sdg', 'h', 'z'), ('z', 'h', 'z'), # u3 gates ( 'x', ), ('y', ), ('s', 'x'), ('sdg', 'x') ] return labels[j] _CLIFFORD_GATES = [ (IGate(), ), (SGate(), ), (SdgGate(), ), (ZGate(), ), # u2 gates (HGate(), ), (HGate(), ZGate()), (ZGate(), HGate()), (HGate(), SGate()), (SGate(), HGate()), (HGate(), SdgGate()), (SdgGate(), HGate()), (SGate(), HGate(), SGate()), (SdgGate(), HGate(), SGate()), (ZGate(), HGate(), SGate()), (SGate(), HGate(), SdgGate()),
def approximate_quantum_error(error, *, operator_string=None, operator_dict=None, operator_list=None): r""" Return a ``QuantumError`` object that approximates an error as a mixture of specified operators (channels). The approximation is done by minimizing the Hilbert-Schmidt distance between the process matrix of the target error channel (:math:`T`) and the process matrix of the output channel (:math:`S = \sum_i{p_i S_i}`), i.e. :math:`Tr[(T-S)^\dagger (T-S)]`, where :math:`[p_1, p_2, ..., p_n]` denote probabilities and :math:`[S_1, S_2, ..., S_n]` denote basis operators (channels). See `arXiv:1207.0046 <http://arxiv.org/abs/1207.0046>`_ for the details. Args: error (QuantumError or QuantumChannel): the error to be approximated. The number of qubits must be 1 or 2. operator_string (string): a name for a pre-made set of building blocks for the output channel (Default: None). Possible values are ``'pauli'``, ``'reset'``, ``'clifford'``. operator_dict (dict): a dictionary whose values are the building blocks for the output channel (Default: None). E.g. {"x": XGate(), "y": YGate()}, keys "x" and "y" are not used in transformation. operator_list (list): list of building block operators for the output channel (Default: None). E.g. [XGate(), YGate()] Returns: QuantumError: the approximate quantum error. Raises: NoiseError: if any invalid argument is specified or approximation failed. MissingOptionalLibraryError: if cvxpy is not installed. Note: The operator input precedence is: ``list`` < ``dict`` < ``string``. If a ``string`` is given, ``dict`` is overwritten; if a ``dict`` is given, ``list`` is overwritten. The ``string`` supports only 1- or 2-qubit errors and its possible values are ``'pauli'``, ``'reset'``, ``'clifford'``. The ``'clifford'`` does not support 2-qubit errors. """ if not isinstance(error, (QuantumError, QuantumChannel)): warnings.warn( 'Support of types other than QuantumError or QuantumChannel for error' ' to be approximated has been deprecated as of qiskit-aer 0.10.0' ' and will be removed no earlier than 3 months from that release date.', DeprecationWarning, stacklevel=2) if isinstance(error, tuple) and isinstance(error[0], np.ndarray): error = list(error) if isinstance(error, list) or \ (isinstance(error, tuple) and isinstance(error[0], list)): # first case for ordinary Kraus [A_i, B_i] # second case for generalized Kraus ([A_i], [B_i]) error = Kraus(error) else: raise NoiseError( f"Invalid input error type: {error.__class__.__name__}") if error.num_qubits > 2: raise NoiseError("Only 1-qubit and 2-qubit noises can be converted, " f"{error.num_qubits}-qubit noise found in model") if operator_string is not None: valid_operator_strings = _PRESET_OPERATOR_TABLE.keys() operator_string = operator_string.lower() if operator_string not in valid_operator_strings: raise NoiseError( f"{operator_string} is not a valid operator_string. " f"It must be one of {valid_operator_strings}") try: operator_list = _PRESET_OPERATOR_TABLE[operator_string][ error.num_qubits] except KeyError: raise NoiseError( f"Preset '{operator_string}' operators do not support the " f"approximation of errors with {error.num_qubits} qubits") if operator_dict is not None: _, operator_list = zip(*operator_dict.items()) if operator_list is not None: if not isinstance(operator_list, Sequence): raise NoiseError( "operator_list is not a sequence: {}".format(operator_list)) if operator_list and isinstance(operator_list[0], Sequence) and isinstance( operator_list[0][0], np.ndarray): warnings.warn( 'Accepting a sequence of Kraus matrices (list of numpy arrays)' ' as an operator_list has been deprecated as of qiskit-aer 0.10.0' ' and will be removed no earlier than 3 months from that release date.' ' Please use a sequence of Kraus objects instead.', DeprecationWarning, stacklevel=2) operator_list = [Kraus(op) for op in operator_list] try: channel_list = [ op if isinstance(op, QuantumChannel) else QuantumError([(op, 1)]) for op in operator_list ] # preserve operator_list except NoiseError: raise NoiseError( f"Invalid type found in operator list: {operator_list}") probabilities = _transform_by_operator_list(channel_list, error)[1:] identity_prob = np.round(1 - sum(probabilities), 9) if identity_prob < 0 or identity_prob > 1: raise NoiseError( f"Channel probabilities sum to {1 - identity_prob}") noise_ops = [((IGate(), [0]), identity_prob)] for (operator, probability) in zip(operator_list, probabilities): noise_ops.append((operator, probability)) return QuantumError(noise_ops) raise NoiseError( "Quantum error approximation failed - no approximating operators detected" )
def depolarizing_error(param, num_qubits, standard_gates=None): r""" Return a depolarizing quantum error channel. The depolarizing channel is defined as: .. math:: E(ρ) = (1 - λ) ρ + λ \text{Tr}[ρ] \frac{I}{2^n} with :math:`0 \le λ \le 4^n / (4^n - 1)` where :math:`λ` is the depolarizing error param and :math`n` is the number of qubits. * If :math:`λ = 0` this is the identity channel :math:`E(ρ) = ρ` * If :math:`λ = 1` this is a completely depolarizing channel :math:`E(ρ) = I / 2^n` * If :math:`λ = 4^n / (4^n - 1)` this is a uniform Pauli error channel: :math:`E(ρ) = \sum_j P_j ρ P_j / (4^n - 1)` for all :math:`P_j != I`. Args: param (double): depolarizing error parameter. num_qubits (int): the number of qubits for the error channel. standard_gates (bool): DEPRECATED, if True return the operators as Pauli gates. If false return as unitary gates. (Default: None) Returns: QuantumError: The quantum error object. Raises: NoiseError: If noise parameters are invalid. """ if not isinstance(num_qubits, int) or num_qubits < 1: raise NoiseError("num_qubits must be a positive integer.") # Check that the depolarizing parameter gives a valid CPTP num_terms = 4**num_qubits max_param = num_terms / (num_terms - 1) if param < 0 or param > max_param: raise NoiseError("Depolarizing parameter must be in between 0 " "and {}.".format(max_param)) # Rescale completely depolarizing channel error probs # with the identity component removed prob_iden = 1 - param / max_param prob_pauli = param / num_terms probs = [prob_iden] + (num_terms - 1) * [prob_pauli] if standard_gates is not None: warnings.warn( '"standard_gates" option has been deprecated as of qiskit-aer 0.10.0' ' and will be removed no earlier than 3 months from that release date.', DeprecationWarning, stacklevel=2) circs = [] for pauli_list in it.product( [IGate(), XGate(), YGate(), ZGate()], repeat=num_qubits): qc = QuantumCircuit(num_qubits) for q, pauli in enumerate(pauli_list): if not standard_gates: pauli = UnitaryGate(pauli.to_matrix()) qc.append(pauli, qargs=[q]) circs.append(qc) return QuantumError(zip(circs, probs)) # Generate pauli strings. The order doesn't matter as long # as the all identity string is first. paulis = [ Pauli("".join(tup)) for tup in it.product(['I', 'X', 'Y', 'Z'], repeat=num_qubits) ] return QuantumError(zip(paulis, probs))
def kraus2instructions(kraus_ops, standard_gates, atol=ATOL_DEFAULT): """ Convert a list of Kraus matrices into qobj circuits. If any Kraus operators are a unitary matrix they will be converted into unitary qobj instructions. Identity unitary matrices will also be converted into identity qobj instructions. Args: kraus_ops (list[matrix]): A list of Kraus matrices for a CPTP map. standard_gates (bool): Check if the matrix instruction is a standard instruction (default: True). atol (double): Threshold for testing if probabilities are zero. Returns: list: A list of pairs (p, circuit) where `circuit` is a list of qobj instructions, and `p` is the probability of that circuit for the given error. Raises: NoiseError: If the input Kraus channel is not CPTP. """ warnings.warn( 'kraus2instructions has been deprecated as of qiskit-aer 0.10.0' ' and will be removed no earlier than 3 months from that release date.', DeprecationWarning, stacklevel=2) # Check threshold if atol < 0: raise NoiseError("atol cannot be negative") if atol > 1e-5: raise NoiseError( "atol value is too large. It should be close to zero.") # Check CPTP if not Kraus(kraus_ops).is_cptp(atol=atol): raise NoiseError("Input Kraus channel is not CPTP.") # Get number of qubits num_qubits = int(np.log2(len(kraus_ops[0]))) if len(kraus_ops[0]) != 2**num_qubits: raise NoiseError("Input Kraus channel is not a multi-qubit channel.") # Check if each matrix is a: # 1. scaled identity matrix # 2. scaled non-identity unitary matrix # 3. a non-unitary Kraus operator # Probabilities prob_identity = 0 prob_unitary = 0 # total probability of all unitary ops (including id) prob_kraus = 0 # total probability of non-unitary ops probabilities = [] # initialize with probability of Identity # Matrices unitaries = [] # non-identity unitaries non_unitaries = [] # non-unitary Kraus matrices for mat in kraus_ops: # Get the value of the maximum diagonal element # of op.H * op for rescaling # pylint: disable=no-member prob = abs(max(np.diag(np.conj(np.transpose(mat)).dot(mat)))) if prob > 0.0: if abs(prob - 1) > 0.0: # Rescale the operator by square root of prob rescaled_mat = np.array(mat) / np.sqrt(prob) else: rescaled_mat = mat # Check if identity operator if is_identity_matrix(rescaled_mat, ignore_phase=True): prob_identity += prob prob_unitary += prob # Check if unitary elif is_unitary_matrix(rescaled_mat): probabilities.append(prob) prob_unitary += prob unitaries.append(rescaled_mat) # Non-unitary op else: non_unitaries.append(mat) # Check probabilities prob_kraus = 1 - prob_unitary if prob_unitary - 1 > atol: raise NoiseError("Invalid kraus matrices: unitary probability " "{} > 1".format(prob_unitary)) if prob_unitary < -atol: raise NoiseError("Invalid kraus matrices: unitary probability " "{} < 1".format(prob_unitary)) if prob_identity - 1 > atol: raise NoiseError("Invalid kraus matrices: identity probability " "{} > 1".format(prob_identity)) if prob_identity < -atol: raise NoiseError("Invalid kraus matrices: identity probability " "{} < 1".format(prob_identity)) if prob_kraus - 1 > atol: raise NoiseError("Invalid kraus matrices: non-unitary probability " "{} > 1".format(prob_kraus)) if prob_kraus < -atol: raise NoiseError("Invalid kraus matrices: non-unitary probability " "{} < 1".format(prob_kraus)) # Build qobj instructions instructions = [] qubits = list(range(num_qubits)) # Add unitary instructions for unitary in unitaries: instructions.append( _make_unitary_instruction( unitary, qubits, standard_gates=standard_gates)) # Add identity instruction if prob_identity > atol: if abs(prob_identity - 1) < atol: probabilities.append(1) else: probabilities.append(prob_identity) instructions.append([(IGate(), [0])]) # Add Kraus if prob_kraus < atol: # No Kraus operators return zip(instructions, probabilities) if prob_kraus < 1: # Rescale kraus operators by probabilities non_unitaries = [ np.array(op) / np.sqrt(prob_kraus) for op in non_unitaries ] with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=DeprecationWarning, module="qiskit.providers.aer.noise.errors.errorutils") instructions.append(make_kraus_instruction(non_unitaries, qubits)) probabilities.append(prob_kraus) # Normalize probabilities to account for any rounding errors probabilities = list(np.array(probabilities) / np.sum(probabilities)) return zip(instructions, probabilities)
def _standard_gate_instruction(instruction, ignore_phase=True): """Temporary function to create Instruction objects from a json string, which is necessary for creating a new QuantumError object from deprecated json-based input. Note that the type of returned object is different from the deprecated standard_gate_instruction. TODO: to be removed after deprecation period. Args: instruction (dict): A qobj instruction. ignore_phase (bool): Ignore global phase on unitary matrix in comparison to canonical unitary. Returns: list: a list of (instructions, qubits) equivalent to in input instruction. """ gate = { "id": IGate(), "x": XGate(), "y": YGate(), "z": ZGate(), "h": HGate(), "s": SGate(), "sdg": SdgGate(), "t": TGate(), "tdg": TdgGate(), "cx": CXGate(), "cz": CZGate(), "swap": SwapGate() } name = instruction.get("name", None) qubits = instruction["qubits"] if name in gate: return [(gate[name], qubits)] if name not in ["mat", "unitary", "kraus"]: return [instruction] params = instruction["params"] with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=DeprecationWarning, module="qiskit.providers.aer.noise.errors.errorutils") # Check for single-qubit reset Kraus if name == "kraus": if len(qubits) == 1: superop = SuperOp(Kraus(params)) # Check if reset to |0> reset0 = reset_superop(1) if superop == reset0: return [(Reset(), [0])] # Check if reset to |1> reset1 = reset0.compose(Operator(standard_gate_unitary('x'))) if superop == reset1: return [(Reset(), [0]), (XGate(), [0])] return [instruction] # Check single qubit gates mat = params[0] if len(qubits) == 1: # Check clifford gates for j in range(24): if matrix_equal( mat, single_qubit_clifford_matrix(j), ignore_phase=ignore_phase): return [(gate, [0]) for gate in _CLIFFORD_GATES[j]] # Check t gates for name in ["t", "tdg"]: if matrix_equal( mat, standard_gate_unitary(name), ignore_phase=ignore_phase): return [(gate[name], qubits)] # TODO: u1,u2,u3 decomposition # Check two qubit gates if len(qubits) == 2: for name in ["cx", "cz", "swap"]: if matrix_equal( mat, standard_gate_unitary(name), ignore_phase=ignore_phase): return [(gate[name], qubits)] # Check reversed CX if matrix_equal( mat, standard_gate_unitary("cx_10"), ignore_phase=ignore_phase): return [(CXGate(), [qubits[1], qubits[0]])] # Check 2-qubit Pauli's paulis = ["id", "x", "y", "z"] for pauli0 in paulis: for pauli1 in paulis: pmat = np.kron( standard_gate_unitary(pauli1), standard_gate_unitary(pauli0)) if matrix_equal(mat, pmat, ignore_phase=ignore_phase): if pauli0 == "id": return [(gate[pauli1], [qubits[1]])] elif pauli1 == "id": return [(gate[pauli0], [qubits[0]])] else: return [(gate[pauli0], [qubits[0]]), (gate[pauli1], [qubits[1]])] # Check three qubit toffoli if len(qubits) == 3: if matrix_equal( mat, standard_gate_unitary("ccx_012"), ignore_phase=ignore_phase): return [(CCXGate(), qubits)] if matrix_equal( mat, standard_gate_unitary("ccx_021"), ignore_phase=ignore_phase): return [(CCXGate(), [qubits[0], qubits[2], qubits[1]])] if matrix_equal( mat, standard_gate_unitary("ccx_120"), ignore_phase=ignore_phase): return [(CCXGate(), [qubits[1], qubits[2], qubits[0]])] # Else return input in return [instruction]