def test_inverse(self, class_name, gate_class): """Verify self-inverse pair yield identity for all standard gates.""" free_params = _get_free_params(gate_class) n_params = len(free_params) float_vector = [0.1 + 0.1*i for i in range(n_params)] if class_name in ('MCPhaseGate', 'MCU1Gate'): float_vector = float_vector[:-1] gate = gate_class(*float_vector, num_ctrl_qubits=2) elif class_name in ('MCXGate', 'MCXGrayCode', 'MCXRecursive', 'MCXVChain'): num_ctrl_qubits = 3 float_vector = float_vector[:-1] gate = gate_class(num_ctrl_qubits, *float_vector) elif class_name == 'MSGate': num_qubits = 3 float_vector = float_vector[:-1] gate = gate_class(num_qubits, *float_vector) elif class_name == 'PauliGate': pauli_string = "IXYZ" gate = gate_class(pauli_string) else: gate = gate_class(*float_vector) from qiskit.quantum_info.operators.predicates import is_identity_matrix self.assertTrue(is_identity_matrix(Operator(gate).dot(gate.inverse()).data)) if gate.definition is not None: self.assertTrue(is_identity_matrix(Operator(gate).dot(gate.definition.inverse()).data)) self.assertTrue(is_identity_matrix(Operator(gate).dot(gate.inverse().definition).data))
def ideal(self): """Return True if this error object is composed only of identity operations. Note that the identity check is best effort and up to global phase.""" for circ in self.circuits: try: # Circuit-level identity check for clifford Circuits clifford = Clifford(circ) if clifford != Clifford(np.eye(2 * circ.num_qubits, dtype=bool)): return False except QiskitError: pass # Component-wise check for non-Clifford circuits for op, _, _ in circ: if isinstance(op, IGate): continue if isinstance(op, PauliGate): if op.params[0].replace('I', ''): return False else: # Convert to Kraus and check if identity kmats = Kraus(op).data if len(kmats) > 1: return False if not is_identity_matrix(kmats[0], ignore_phase=True, atol=self.atol, rtol=self.rtol): return False return True
def is_cptp(self): """Return True if completely-positive trace-preserving.""" # We convert to the Choi representation to check if CPTP if self._data[1] is not None: return False check = np.dot(np.transpose(np.conj(self._data[0])), self._data[0]) return is_identity_matrix(check, rtol=self.rtol, atol=self.atol)
def is_cptp(self): """Return True if completely-positive trace-preserving.""" if self._data[1] is not None: return False accum = 0j for op in self._data[0]: accum += np.dot(np.transpose(np.conj(op)), op) return is_identity_matrix(accum, rtol=self.rtol, atol=self.atol)
def _is_tp(self): """Test if Choi-matrix is trace-preserving (TP)""" # Check if the partial trace is the identity matrix d_in, d_out = self.dims mat = np.trace( np.reshape(self._data, (d_in, d_out, d_in, d_out)), axis1=1, axis2=3) return is_identity_matrix(mat, rtol=self.rtol, atol=self.atol)
def is_cptp(self, atol=None, rtol=None): """Return True if completely-positive trace-preserving.""" if atol is None: atol = self.atol if rtol is None: rtol = self.rtol if self._data[1] is not None: return False check = np.dot(np.transpose(np.conj(self._data[0])), self._data[0]) return is_identity_matrix(check, rtol=self.rtol, atol=self.atol)
def _is_tp(self, choi=None): """Test if Choi-matrix is trace-preserving (TP)""" if choi is None: choi = _to_choi(self.rep, self._data, *self.dim) # Check if the partial trace is the identity matrix d_in, d_out = self.dim mat = np.trace(np.reshape(choi, (d_in, d_out, d_in, d_out)), axis1=1, axis2=3) return is_identity_matrix(mat, rtol=self._rtol, atol=self._atol)
def _is_tp_helper(self, choi, atol, rtol): """Test if Choi-matrix is trace-preserving (TP)""" if atol is None: atol = self.atol if rtol is None: rtol = self.rtol # Check if the partial trace is the identity matrix d_in, d_out = self.dim mat = np.trace( np.reshape(choi, (d_in, d_out, d_in, d_out)), axis1=1, axis2=3) return is_identity_matrix(mat, rtol=rtol, atol=atol)
def mixed_unitary_error(noise_ops, standard_gates=True): """ 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 matricies. standard_gates (bool): Check if input matrices are standard gates. Returns: QuantumError: The quantum error object. Raises: NoiseError: if error parameters are invalid. """ # 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 = qubits_from_mat(noise_ops[0][0]) qubits = list(range(num_qubits)) for unitary, prob in noise_ops: # Check unitary if qubits_from_mat(unitary) != num_qubits: 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: instr = make_unitary_instruction(unitary, qubits, standard_gates=standard_gates) instructions.append(instr) instructions_probs.append(prob) if prob_identity > 0: instructions.append([{"name": "id", "qubits": [0]}]) instructions_probs.append(prob_identity) return QuantumError(zip(instructions, instructions_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. """ # 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 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([{"name": "id", "qubits": [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 ] 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 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))