def compose(self, other, qargs=None, front=False): """Return the composition channel self∘other. Args: other (QuantumChannel): a quantum channel subclass. qargs (list): a list of subsystem positions to compose other on. front (bool): If False compose in standard order other(self(input)) otherwise compose in reverse order self(other(input)) [default: False] Returns: Stinespring: The composition channel as a Stinespring object. Raises: QiskitError: if other cannot be converted to a channel or has incompatible dimensions. """ if qargs is not None: return Stinespring( SuperOp(self).compose(other, qargs=qargs, front=front)) # Convert other to Kraus if not isinstance(other, Kraus): other = Kraus(other) # Check dimensions match up if front and self._input_dim != other._output_dim: raise QiskitError( 'input_dim of self must match output_dim of other') if not front and self._output_dim != other._input_dim: raise QiskitError( 'input_dim of other must match output_dim of self') # Since we cannot directly compose two channels in Stinespring # representation we convert to the Kraus representation return Stinespring(Kraus(self).compose(other, front=front))
def test_operator_to_kraus(self): """Test Operator to Kraus transformation.""" # Test unitary channels for mat in self.unitary_mat: chan1 = Kraus(mat) chan2 = Kraus(Operator(mat)) self.assertEqual(chan1, chan2)
def test_dot_both_kraus(self): """Test dot of two kraus errors""" a_0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex) a_1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex) b_0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex) b_1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex) # Use quantum channels for reference target = SuperOp(Kraus([b_0, b_1]).compose(Kraus([a_0, a_1]))) # dot method error = QuantumError([a_0, a_1]).dot(QuantumError([b_0, b_1])) kraus, prob = error.error_term(0) self.assertEqual(prob, 1) self.assertEqual(kraus[0]['name'], 'kraus') self.assertEqual(kraus[0]['qubits'], [0]) error_superop = SuperOp(Kraus(kraus[0]['params'])) self.assertEqual(target, error_superop, msg="Incorrect kraus dot method") # * method error = QuantumError([a_0, a_1]) * QuantumError([b_0, b_1]) kraus, prob = error.error_term(0) self.assertEqual(prob, 1) self.assertEqual(kraus[0]['name'], 'kraus') self.assertEqual(kraus[0]['qubits'], [0]) error_superop = SuperOp(Kraus(kraus[0]['params'])) self.assertEqual(target, error_superop, msg="Incorrect kraus dot method")
def test_unitary_to_kraus(self): """Test UnitaryChannel to Kraus transformation.""" # Test unitary channels for mat in self.unitary_mat: chan1 = Kraus(mat) chan2 = Kraus(UnitaryChannel(mat)) self.assertEqual(chan1, chan2)
def test_kraus_to_operator(self): """Test Kraus to Operator transformation.""" for mat in self.unitary_mat: chan1 = Operator(mat) chan2 = Operator(Kraus(mat)) self.assertTrue( matrix_equal(chan2.data, chan1.data, ignore_phase=True)) self.assertRaises(QiskitError, Operator, Kraus(self.depol_kraus(0.5)))
def test_kraus_to_unitary(self): """Test Kraus to UnitaryChannel transformation.""" for mat in self.unitary_mat: chan1 = UnitaryChannel(mat) chan2 = UnitaryChannel(Kraus(mat)) self.assertTrue( matrix_equal(chan2.data, chan1.data, ignore_phase=True)) self.assertRaises(QiskitError, UnitaryChannel, Kraus(self.depol_kraus(0.5)))
def test_to_quantumchannel_kraus(self): """Test to_quantumchannel for Kraus inputs.""" a_0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex) a_1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex) b_0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex) b_1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex) target = SuperOp(Kraus([a_0, a_1])).tensor(SuperOp(Kraus([b_0, b_1]))) error = QuantumError([a_0, a_1]).tensor(QuantumError([b_0, b_1])) self.assertEqual(target, error.to_quantumchannel())
def test_to_channel_kraus(self): """Test to_channel for Kraus inputs.""" A0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex) A1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex) B0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex) B1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex) target = SuperOp(Kraus([A0, A1])).tensor(SuperOp(Kraus([B0, B1]))) error = QuantumError([A0, A1]).tensor(QuantumError([B0, B1])) self.assertEqual(target, error.to_channel())
def test_kraus_to_ptm(self): """Test Kraus to PTM transformation.""" # Test unitary channels for mat, ptm in zip(self.unitary_mat, self.unitary_ptm): chan1 = PTM(ptm) chan2 = PTM(Kraus(mat)) self.assertEqual(chan1, chan2) # Test depolarizing channels for p in [0.25, 0.5, 0.75, 1]: chan1 = PTM(self.depol_ptm(p)) chan2 = PTM(Kraus(self.depol_kraus(p))) self.assertEqual(chan1, chan2)
def test_stinespring_to_kraus(self): """Test Stinespring to Kraus transformation.""" # Test unitary channels for mat in self.unitary_mat: chan1 = Kraus(mat) chan2 = Kraus(Stinespring(mat)) self.assertEqual(chan1, chan2) # Test depolarizing channels for p in [0.25, 0.5, 0.75, 1]: chan1 = Kraus(self.depol_kraus(p)) chan2 = Kraus(Stinespring(self.depol_stine(p))) self.assertEqual(chan1, chan2)
def test_kraus_to_chi(self): """Test Kraus to Chi transformation.""" # Test unitary channels for mat, chi in zip(self.unitary_mat, self.unitary_chi): chan1 = Chi(chi) chan2 = Chi(Kraus(mat)) self.assertEqual(chan1, chan2) # Test depolarizing channels for p in [0.25, 0.5, 0.75, 1]: chan1 = Chi(self.depol_chi(p)) chan2 = Chi(Kraus(self.depol_kraus(p))) self.assertEqual(chan1, chan2)
def test_kraus_to_superop(self): """Test Kraus to SuperOp transformation.""" # Test unitary channels for mat, sop in zip(self.unitary_mat, self.unitary_sop): chan1 = SuperOp(sop) chan2 = SuperOp(Kraus(mat)) self.assertEqual(chan1, chan2) # Test depolarizing channels for p in [0.25, 0.5, 0.75, 1]: chan1 = SuperOp(self.depol_sop(p)) chan2 = SuperOp(Kraus(self.depol_kraus(p))) self.assertEqual(chan1, chan2)
def test_ptm_to_stinespring(self): """Test PTM to Stinespring transformation.""" # Test unitary channels for mat, ptm in zip(self.unitary_mat, self.unitary_ptm): chan1 = Kraus(mat) chan2 = Kraus(PTM(ptm)) self.assertTrue( matrix_equal(chan2.data[0], chan1.data[0], ignore_phase=True)) # Test depolarizing channels rho = DensityMatrix(np.diag([1, 0])) for p in [0.25, 0.5, 0.75, 1]: target = rho.evolve(Stinespring(self.depol_stine(p))) output = rho.evolve(Stinespring(PTM(self.depol_ptm(p)))) self.assertEqual(output, target)
def test_chi_to_kraus(self): """Test Chi to Kraus transformation.""" # Test unitary channels for mat, chi in zip(self.unitary_mat, self.unitary_chi): chan1 = Kraus(mat) chan2 = Kraus(Chi(chi)) self.assertTrue( matrix_equal(chan2.data[0], chan1.data[0], ignore_phase=True)) # Test depolarizing channels rho = DensityMatrix(np.diag([1, 0])) for p in [0.25, 0.5, 0.75, 1]: target = rho.evolve(Kraus(self.depol_kraus(p))) output = rho.evolve(Kraus(Chi(self.depol_chi(p)))) self.assertEqual(output, target)
def test_kraus_to_stinespring(self): """Test Kraus to Stinespring transformation.""" # Test unitary channels for mat in self.unitary_mat: chan1 = Stinespring(mat) chan2 = Stinespring(Kraus(mat)) self.assertTrue( matrix_equal(chan2.data[0], chan1.data[0], ignore_phase=True)) # Test depolarizing channels rho = np.diag([1, 0]) for p in [0.25, 0.5, 0.75, 1]: targ = Stinespring(self.depol_stine(p))._evolve(rho) chan = Stinespring(Kraus(self.depol_kraus(p))) self.assertAllClose(chan._evolve(rho), targ)
def test_chi_to_kraus(self): """Test Chi to Kraus transformation.""" # Test unitary channels for mat, chi in zip(self.unitary_mat, self.unitary_chi): chan1 = Kraus(mat) chan2 = Kraus(Chi(chi)) self.assertTrue( matrix_equal(chan2.data[0], chan1.data[0], ignore_phase=True)) # Test depolarizing channels rho = np.diag([1, 0]) for p in [0.25, 0.5, 0.75, 1]: targ = Kraus(self.depol_kraus(p))._evolve(rho) chan = Kraus(Chi(self.depol_chi(p))) self.assertAllClose(chan._evolve(rho), targ)
def test_ptm_to_stinespring(self): """Test PTM to Stinespring transformation.""" # Test unitary channels for mat, ptm in zip(self.unitary_mat, self.unitary_ptm): chan1 = Kraus(mat) chan2 = Kraus(PTM(ptm)) self.assertTrue( matrix_equal(chan2.data[0], chan1.data[0], ignore_phase=True)) # Test depolarizing channels rho = np.diag([1, 0]) for p in [0.25, 0.5, 0.75, 1]: targ = Stinespring(self.depol_stine(p))._evolve(rho) chan = Stinespring(PTM(self.depol_ptm(p))) self.assertAllClose(chan._evolve(rho), targ)
def test_compose_both_kraus(self): """Test composition of two kraus errors""" A0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex) A1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex) B0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex) B1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex) # Use quantum channels for reference target = SuperOp(Kraus([A0, A1]).compose(Kraus([B0, B1]))) error = QuantumError([A0, A1]).compose(QuantumError([B0, B1])) kraus, p = error.error_term(0) self.assertEqual(p, 1) self.assertEqual(kraus[0]['name'], 'kraus') self.assertEqual(kraus[0]['qubits'], [0]) error_superop = SuperOp(Kraus(kraus[0]['params'])) self.assertEqual(target, error_superop, msg="Incorrect compose kraus")
def standard_instruction_channel(instr): """Return the SuperOp channel for a standard instruction.""" warnings.warn( 'standard_instruction_channel 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 if standard operator oper = standard_instruction_operator(instr) if oper is not None: return SuperOp(oper) # Convert to dict (for QobjInstruction types) if hasattr(instr, 'as_dict'): instr = instr.as_dict() # Get name and parameters name = instr.get('name', "") # Check if reset instruction if name == 'reset': # params should be the number of qubits being reset num_qubits = len(instr['qubits']) with warnings.catch_warnings(): warnings.simplefilter("ignore") res = reset_superop(num_qubits) return res # Check if Kraus instruction if name == 'kraus': params = instr['params'] return SuperOp(Kraus(params)) return None
def compose(self, other, qargs=None, front=False): """Return the composed quantum channel self @ other. Args: other (QuantumChannel): a quantum channel. qargs (list or None): a list of subsystem positions to apply other on. If None apply on all subsystems [default: None]. front (bool): If True compose using right operator multiplication, instead of left multiplication [default: False]. Returns: Stinespring: The quantum channel self @ other. Raises: QiskitError: if other cannot be converted to a Stinespring or has incompatible dimensions. Additional Information: Composition (``@``) is defined as `left` matrix multiplication for :class:`SuperOp` matrices. That is that ``A @ B`` is equal to ``B * A``. Setting ``front=True`` returns `right` matrix multiplication ``A * B`` and is equivalent to the :meth:`dot` method. """ if qargs is None: qargs = getattr(other, 'qargs', None) if qargs is not None: return Stinespring( SuperOp(self).compose(other, qargs=qargs, front=front)) # Otherwise we convert via Kraus representation rather than # superoperator to avoid unnecessary representation conversions return Stinespring(Kraus(self).compose(other, front=front))
def compose(self, other, qargs=None, front=False): if qargs is None: qargs = getattr(other, "qargs", None) if qargs is not None: return Stinespring( SuperOp(self).compose(other, qargs=qargs, front=front)) # Otherwise we convert via Kraus representation rather than # superoperator to avoid unnecessary representation conversions return Stinespring(Kraus(self).compose(other, front=front))
def _kraus_to_other_single(self, rep, qubits_test_cases, repetitions): """Test single Kraus to Other evolution.""" for nq in qubits_test_cases: dim = 2**nq for _ in range(repetitions): rho = self.rand_rho(dim) kraus = self.rand_kraus(dim, dim, dim**2) chan = Kraus(kraus) rho1 = DensityMatrix(rho).evolve(chan).data rho2 = DensityMatrix(rho).evolve(rep(chan)).data assert_allclose(rho1, rho2)
def _kraus_to_other_single(self, rep, qubits_test_cases, repetitions): """Test single Kraus to Other evolution.""" for nq in qubits_test_cases: dim = 2**nq for _ in range(repetitions): rho = self.rand_rho(dim) kraus = self.rand_kraus(dim, dim, dim**2) chan1 = Kraus(kraus) rho1 = chan1._evolve(rho) chan2 = rep(chan1) rho2 = chan2._evolve(rho) self.assertAllClose(rho1, rho2)
def _kraus_to_other_double(self, rep, qubits_test_cases, repetitions): """Test double Kraus to Other evolution.""" for nq in qubits_test_cases: dim = 2**nq for _ in range(repetitions): rho = self.rand_rho(dim) kraus_l = self.rand_kraus(dim, dim, dim**2) kraus_r = self.rand_kraus(dim, dim, dim**2) chan = Kraus((kraus_l, kraus_r)) rho1 = DensityMatrix(rho).evolve(chan).data rho2 = DensityMatrix(rho).evolve(rep(chan)).data self.assertAllClose(rho1, rho2)
def _chanmul(self, other, qargs=None, left_multiply=False): """Multiply two quantum channels. Args: other (QuantumChannel): a quantum channel. qargs (list): a list of subsystem positions to compose other on. left_multiply (bool): If True return other * self If False return self * other [Default:False] Returns: Stinespring: The composition channel as a Stinespring object. Raises: QiskitError: if other is not a QuantumChannel subclass, or has incompatible dimensions. """ if qargs is not None: return Stinespring( SuperOp(self)._chanmul(other, qargs=qargs, left_multiply=left_multiply)) # Convert other to Kraus if not isinstance(other, Kraus): other = Kraus(other) # Check dimensions match up if not left_multiply and self._input_dim != other._output_dim: raise QiskitError( 'input_dim of self must match output_dim of other') if left_multiply and self._output_dim != other._input_dim: raise QiskitError( 'input_dim of other must match output_dim of self') # Since we cannot directly compose two channels in Stinespring # representation we convert to the Kraus representation return Stinespring( Kraus(self)._chanmul(other, left_multiply=left_multiply))
def make_kraus_instruction(mats, qubits): """Return a qobj instruction for a Kraus error. Args: mats (list[matrix]): A list of square or diagonal Kraus matrices. qubits (list[int]): The qubits the matrix is applied to. Returns: dict: The qobj instruction object. Raises: NoiseError: if the input is not a CPTP Kraus channel. """ kraus = Kraus(mats) if not kraus.is_cptp() or kraus._input_dim != kraus._output_dim: raise NoiseError("Input Kraus matrices are not a CPTP channel.") if isinstance(qubits, int): qubits = [qubits] return [{"name": "kraus", "qubits": qubits, "params": kraus.data}]
def circuit2superop(circuit, min_qubits=1): """Return the SuperOp for a standard instruction.""" # Get number of qubits max_qubits = 1 for instr in circuit: qubits = [] if hasattr(instr, 'qubits'): qubits = instr.qubits elif isinstance(instr, dict): qubits = instr.get('qubits', []) max_qubits = max(max_qubits, 1 + max(qubits)) num_qubits = max(max_qubits, min_qubits) # Initialize N-qubit identity superoperator superop = SuperOp(np.eye(4**num_qubits)) # compose each circuit element with the superoperator for instr in circuit: name = None qubits = None params = None if isinstance(instr, dict): # Parse from plain dictionary qobj instruction name = instr['name'] qubits = instr.get('qubits') params = instr.get('params') else: # Parse from QasmQobjInstruction if hasattr(instr, 'name'): name = instr.name if hasattr(instr, 'qubits'): qubits = instr.qubits if hasattr(instr, 'params'): params = instr.params if name is 'reset': instr_op = reset_superop(len(qubits)) elif name is 'kraus': instr_op = Kraus(params) else: instr_op = SuperOp(standard_instruction_operator(name, params)) superop = superop.compose(instr_op, qubits=qubits) return superop
def standard_instruction_channel(instr): """Return the SuperOp channel for a standard instruction.""" # Check if standard operator oper = standard_instruction_operator(instr) if oper is not None: return SuperOp(oper) # Convert to dict (for QobjInstruction types) if hasattr(instr, 'as_dict'): instr = instr.as_dict() # Get name and parameters name = instr.get('name', "") # Check if reset instruction if name == 'reset': # params should be the number of qubits being reset num_qubits = len(instr['qubits']) return reset_superop(num_qubits) # Check if Kraus instruction if name == 'kraus': params = instr['params'] return SuperOp(Kraus(params)) return None
def make_kraus_instruction(mats, qubits): """Return a qobj instruction for a Kraus error. Args: mats (list[matrix]): A list of square or diagonal Kraus matrices. qubits (list[int]): The qubits the matrix is applied to. Returns: dict: The qobj instruction object. Raises: NoiseError: if the input is not a CPTP Kraus channel. """ warnings.warn( 'make_kraus_instruction 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) kraus = Kraus(mats) if not kraus.is_cptp() or kraus._input_dim != kraus._output_dim: raise NoiseError("Input Kraus matrices are not a CPTP channel.") if isinstance(qubits, int): qubits = [qubits] return [{"name": "kraus", "qubits": qubits, "params": kraus.data}]
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)