def _compose_instr(instr0, instr1, num_qubits): """Helper function for compose a kraus with another instruction.""" # If one of the instructions is an identity we only need # to return the other instruction if instr0['name'] == 'id': return instr1 if instr1['name'] == 'id': return instr0 # Convert to ops op0 = QuantumError._instr2op(instr0) op1 = QuantumError._instr2op(instr1) # Check if at least one of the instructions is a channel # and if so convert both to SuperOp representation if isinstance(op0, (SuperOp, Kraus)) or isinstance(op1, (SuperOp, Kraus)): name = 'kraus' op0 = SuperOp(op0) op1 = SuperOp(op1) else: name = 'unitary' # Check qubits for compositions qubits0 = instr0['qubits'] qubits1 = instr1['qubits'] if qubits0 == qubits1: composed = op0.compose(op1) qubits = qubits0 else: # If qubits don't match we compose with total number of qubits # for the error if name == 'kraus': composed = SuperOp(np.eye(4**num_qubits)) else: composed = Operator(np.eye(2**num_qubits)) composed = composed.compose(op0, qargs=qubits0).compose(op1, qargs=qubits1) qubits = list(range(num_qubits)) # Get instruction params if name == 'kraus': params = Kraus(composed).data else: params = [composed.data] return {'name': name, 'qubits': qubits, 'params': params}
def _combine_kraus(noise_ops, num_qubits): """Combine any noise circuits containing only Kraus instructions.""" kraus_instr = [] kraus_probs = [] new_circuits = [] new_probs = [] # Partion circuits into Kraus and non-Kraus for circuit, prob in noise_ops: if len(circuit) == 1 and circuit[0]['name'] == 'kraus': kraus_instr.append(circuit[0]) kraus_probs.append(prob) else: new_circuits.append(circuit) new_probs.append(prob) # Combine matching Kraus instructions via Choi rep if len(kraus_probs) == 1: new_circuits.append([kraus_instr[0]]) new_probs.append(kraus_probs[0]) elif len(kraus_probs) > 1: dim = 2**num_qubits iden = SuperOp(np.eye(dim**2)) choi_sum = Choi(np.zeros((dim**2, dim**2))) for prob, instr in zip(kraus_probs, kraus_instr): choi_sum = choi_sum + prob * iden.compose( Kraus(instr['params']), instr['qubits']) # Renormalize the Choi operator to find probability # of Kraus error chan_prob = abs(np.trace(choi_sum.data) / dim) chan_instr = { "name": "kraus", "qubits": list(range(num_qubits)), "params": Kraus(choi_sum / chan_prob).data } new_circuits.append([chan_instr]) new_probs.append(chan_prob) return list(zip(new_circuits, new_probs))
def process_fidelity(channel1, channel2, require_cptp=True): """Return the process fidelity between two quantum channels. This is given by F_p(E1, E2) = Tr[S2^dagger.S1])/dim^2 where S1 and S2 are the SuperOp matrices for channels E1 and E2, and dim is the dimension of the input output statespace. Args: channel1 (QuantumChannel or matrix): a quantum channel or unitary matrix. channel2 (QuantumChannel or matrix): a quantum channel or unitary matrix. require_cptp (bool): require input channels to be CPTP [Default: True]. Returns: array_like: The state fidelity F(state1, state2). Raises: QiskitError: if inputs channels do not have the same dimensions, have different input and output dimensions, or are not CPTP with `require_cptp=True`. """ # First we must determine if input is to be interpreted as a unitary matrix # or as a channel. # If input is a raw numpy array we will interpret it as a unitary matrix. is_cptp1 = None is_cptp2 = None if isinstance(channel1, (list, np.ndarray)): channel1 = Operator(channel1) if require_cptp: is_cptp1 = channel1.is_unitary() if isinstance(channel2, (list, np.ndarray)): channel2 = Operator(channel2) if require_cptp: is_cptp2 = channel2.is_unitary() # Next we convert inputs SuperOp objects # This works for objects that also have a `to_operator` or `to_channel` method s1 = SuperOp(channel1) s2 = SuperOp(channel2) # Check inputs are CPTP if require_cptp: # Only check SuperOp if we didn't already check unitary inputs if is_cptp1 is None: is_cptp1 = s1.is_cptp() if not is_cptp1: raise QiskitError('channel1 is not CPTP') if is_cptp2 is None: is_cptp2 = s2.is_cptp() if not is_cptp2: raise QiskitError('channel2 is not CPTP') # Check dimensions match input_dim1, output_dim1 = s1.dim input_dim2, output_dim2 = s2.dim if input_dim1 != output_dim1 or input_dim2 != output_dim2: raise QiskitError('Input channels must have same size input and output dimensions.') if input_dim1 != input_dim2: raise QiskitError('Input channels have different dimensions.') # Compute process fidelity fidelity = np.trace(s1.compose(s2.adjoint()).data) / (input_dim1 ** 2) return fidelity