def test_multi_qubit_error_transfer_matrix(self):
        """Test the calculation of the multi-qubit transfer matrix"""
        n_cops = 4
        n_nops = 2
        for d, n_dt in zip(rng.integers(3, 7, 10), rng.integers(1, 6, 10)):
            f, n = np.modf(np.log2(d))
            btype = 'Pauli' if f == 0.0 else 'GGM'
            pulse = testutil.rand_pulse_sequence(d, n_dt, n_cops, n_nops,
                                                 btype)
            omega = util.get_sample_frequencies(pulse, n_samples=51)

            # Single spectrum, different spectra for each noise oper
            spectra = [
                1e-8 / omega**2,
                np.outer(1e-7 * (np.arange(n_nops) + 1),
                         400 / (omega**2 + 400))
            ]
            # Cross-correlated spectra which are complex, real part symmetric and
            # imaginary part antisymmetric
            spec = np.tile(1e-8 / abs(omega)**2,
                           (n_nops, n_nops, 1)).astype(complex)
            spec[np.triu_indices(n_nops, 1)].imag = 1e-10 * omega
            spec[np.tril_indices(
                n_nops, -1)].imag = -spec[np.triu_indices(n_nops, 1)].imag
            spectra.append(spec)

            for S in spectra:
                # Assert fidelity is same as computed by infidelity()
                U = ff.error_transfer_matrix(pulse, S, omega)
                # Calculate U in loop
                Up = ff.error_transfer_matrix(pulse,
                                              S,
                                              omega,
                                              memory_parsimonious=True)
                # Calculate second order
                U2 = ff.error_transfer_matrix(pulse,
                                              S,
                                              omega,
                                              second_order=True)
                I_fidelity = ff.infidelity(pulse, S, omega)
                I_transfer = 1 - np.einsum('...ii', U) / d**2
                I_transfer_2 = 1 - np.einsum('...ii', U2) / d**2
                self.assertArrayAlmostEqual(Up, U)
                self.assertArrayAlmostEqual(I_transfer,
                                            I_fidelity.sum(),
                                            atol=1e-4)
                self.assertArrayAlmostEqual(I_transfer_2,
                                            I_fidelity.sum(),
                                            atol=1e-4)
    def test_liouville_to_choi(self):
        """Test converting Liouville superops to choi matrices."""
        for d in rng.integers(2, 9, (15, )):
            # unitary channel
            U = testutil.rand_unit(d, rng.integers(1, 8)).squeeze()
            n = np.log2(d)
            if n % 1 == 0:
                basis = ff.Basis.pauli(int(n))
            else:
                basis = ff.Basis.ggm(d)

            U_sup = superoperator.liouville_representation(U, basis)
            choi = superoperator.liouville_to_choi(U_sup, basis).view(ff.Basis)

            self.assertTrue(choi.isherm)
            self.assertArrayAlmostEqual(np.einsum('...ii', choi), d)

            pulse = testutil.rand_pulse_sequence(d, 1)
            omega = ff.util.get_sample_frequencies(pulse)
            S = 1 / abs(omega)**2

            U_sup = ff.error_transfer_matrix(pulse, S, omega)
            choi = superoperator.liouville_to_choi(U_sup, basis).view(ff.Basis)

            self.assertTrue(choi.isherm)
            self.assertAlmostEqual(np.einsum('ii', choi), d)
    def test_multi_qubit_error_transfer_matrix(self):
        """Test the calculation of the multi-qubit transfer matrix"""
        n_cops = 4
        n_nops = 2
        for d, n_dt in zip(rng.randint(3, 9, 10), rng.randint(1, 11, 10)):
            f, n = np.modf(np.log2(d))
            btype = 'Pauli' if f == 0.0 else 'GGM'
            pulse = testutil.rand_pulse_sequence(d, n_dt, n_cops, n_nops,
                                                 btype)
            omega = util.get_sample_frequencies(pulse, n_samples=51)

            # Assert fidelity is same as computed by infidelity()
            S = 1e-8 / omega**2
            U = ff.error_transfer_matrix(pulse, S, omega)
            # Calculate U in loop
            Up = ff.error_transfer_matrix(pulse,
                                          S,
                                          omega,
                                          memory_parsimonious=True)
            I_fidelity = ff.infidelity(pulse, S, omega)
            I_transfer = 1 - np.einsum('...ii', U) / d**2
            self.assertArrayAlmostEqual(Up, U)
            self.assertArrayAlmostEqual(I_transfer,
                                        I_fidelity.sum(),
                                        atol=1e-4)

            S = np.outer(1e-7 * (np.arange(n_nops) + 1),
                         400 / (omega**2 + 400))
            U = ff.error_transfer_matrix(pulse, S, omega)
            # Calculate U in loop
            Up = ff.error_transfer_matrix(pulse,
                                          S,
                                          omega,
                                          memory_parsimonious=True)
            I_fidelity = ff.infidelity(pulse, S, omega)
            I_transfer = 1 - np.einsum('...ii', U) / d**2
            self.assertArrayAlmostEqual(Up, U)
            self.assertArrayAlmostEqual(I_transfer,
                                        I_fidelity.sum(),
                                        atol=1e-4)

            S = np.tile(1e-8 / abs(omega)**2,
                        (n_nops, n_nops, 1)).astype(complex)
            S[np.triu_indices(n_nops, 1)].imag = 1e-10 * omega
            S[np.tril_indices(n_nops, -1)].imag = \
                - S[np.triu_indices(n_nops, 1)].imag
            U = ff.error_transfer_matrix(pulse, S, omega)
            # Calculate U in loop
            Up = ff.error_transfer_matrix(pulse,
                                          S,
                                          omega,
                                          memory_parsimonious=True)
            I_fidelity = ff.infidelity(pulse, S, omega)
            I_transfer = 1 - np.einsum('...ii', U) / d**2
            self.assertArrayAlmostEqual(Up, U)
            self.assertArrayAlmostEqual(I_transfer,
                                        I_fidelity.sum(),
                                        atol=1e-4)
    def test_infidelity_cnot(self):
        """Compare infidelity to monte carlo results"""
        c_opers = testutil.subspace_opers
        n_opers = c_opers
        c_coeffs, n_coeffs = testutil.c_coeffs, testutil.n_coeffs
        dt = testutil.dt
        infid_MC = testutil.cnot_infid_fast
        A = testutil.A

        # Basis for qubit subspace
        qubit_subspace_basis = ff.Basis(
            [np.pad(b, 1, 'constant') for b in ff.Basis.pauli(2)],
            skip_check=True,
            btype='Pauli')
        complete_basis = ff.Basis(qubit_subspace_basis,
                                  traceless=False,
                                  btype='Pauli')

        identifiers = ['eps_12', 'eps_23', 'eps_34', 'b_12', 'b_23', 'b_34']
        H_c = list(zip(c_opers, c_coeffs, identifiers))
        H_n = list(zip(n_opers, n_coeffs, identifiers))
        cnot = ff.PulseSequence(H_c, H_n, dt, basis=qubit_subspace_basis)
        cnot_full = ff.PulseSequence(H_c, H_n, dt, basis=complete_basis)

        # Manually set dimension of pulse as the dimension of the computational
        # subspace
        cnot.d = 4
        T = dt.sum()

        for f_min, A, alpha, MC, rtol in zip((1 / T, 1e-2 / T), A, (0.0, 0.7),
                                             infid_MC, (0.04, 0.02)):

            omega = np.geomspace(f_min, 1e2, 250) * 2 * np.pi
            S_t, omega_t = ff.util.symmetrize_spectrum(A / omega**alpha, omega)

            infid, xi = ff.infidelity(cnot,
                                      S_t,
                                      omega_t,
                                      identifiers[:3],
                                      return_smallness=True)

            U = ff.error_transfer_matrix(cnot_full, S_t, omega_t,
                                         identifiers[:3])
            infid_P = np.trace(U[:, :16, :16], axis1=1, axis2=2).real / 4**2

            print(np.abs(1 - (infid.sum() / MC)))
            print(np.abs(1 - (infid_P.sum() / MC)))
            self.assertLessEqual(np.abs(1 - (infid.sum() / MC)), rtol)
            self.assertLessEqual(np.abs(1 - (infid_P.sum() / MC)), rtol)
            self.assertLessEqual(infid.sum(), xi**2 / 4)
Exemple #5
0
    def test_error_transfer_matrix(self):
        """Test raises of numeric.error_transfer_matrix."""
        pulse = testutil.rand_pulse_sequence(2, 1, 1, 1)
        omega = testutil.rng.randn(43)
        spectrum = np.ones_like(omega)
        with self.assertRaises(ValueError):
            ff.error_transfer_matrix(pulse, spectrum)

        with self.assertRaises(TypeError):
            ff.error_transfer_matrix(cumulant_function=[1, 2, 3])

        with self.assertRaises(ValueError):
            ff.error_transfer_matrix(cumulant_function=testutil.rng.randn(2, 3, 4))
    def test_liouville_is_CP(self):
        def partial_transpose(A):
            d = A.shape[-1]
            sqd = int(np.sqrt(d))
            return A.reshape(-1, sqd, sqd, sqd,
                             sqd).swapaxes(-1, -3).reshape(A.shape)

        # Partial transpose map should be non-CP
        basis = ff.Basis.pauli(2)
        Phi = ff.basis.expand(partial_transpose(basis), basis).T
        CP = superoperator.liouville_is_CP(Phi, basis)
        self.assertFalse(CP)

        for d in rng.integers(2, 9, (15, )):
            # unitary channel
            U = testutil.rand_unit(d, rng.integers(1, 8)).squeeze()
            n = np.log2(d)
            if n % 1 == 0:
                basis = ff.Basis.pauli(int(n))
            else:
                basis = ff.Basis.ggm(d)

            U_sup = superoperator.liouville_representation(U, basis)
            CP, (D, V) = superoperator.liouville_is_CP(U_sup, basis, True)
            _CP = superoperator.liouville_is_CP(U_sup, basis, False)

            self.assertArrayEqual(CP, _CP)
            self.assertTrue(np.all(CP))
            if U_sup.ndim == 2:
                self.assertIsInstance(CP, (bool, np.bool8))
            else:
                self.assertEqual(CP.shape[0], U_sup.shape[0])
            # Only one nonzero eigenvalue
            self.assertArrayAlmostEqual(D[..., :-1], 0, atol=basis._atol)

            pulse = testutil.rand_pulse_sequence(d, 1)
            omega = ff.util.get_sample_frequencies(pulse)
            S = 1 / abs(omega)**2

            U_sup = ff.error_transfer_matrix(pulse, S, omega)
            CP = superoperator.liouville_is_CP(U_sup, pulse.basis)

            self.assertTrue(np.all(CP))
            self.assertIsInstance(CP, (bool, np.bool8))
    def test_multi_qubit_error_transfer_matrix(self):
        """Test the calculation of the multi-qubit transfer matrix"""
        n_cops = 4
        n_nops = 2
        for d, n_dt in zip(rng.randint(3, 9, 10), rng.randint(1, 11, 10)):
            f, n = np.modf(np.log2(d))
            btype = 'Pauli' if f == 0.0 else 'GGM'
            pulse = testutil.rand_pulse_sequence(d, n_dt, n_cops, n_nops,
                                                 btype)
            omega = ff.util.get_sample_frequencies(pulse, n_samples=51)

            # Assert fidelity is same as computed by infidelity()
            S = 1e-2 / omega**2
            U = ff.error_transfer_matrix(pulse, S, omega)
            # Calculate U in loop
            Up = ff.error_transfer_matrix(pulse,
                                          S,
                                          omega,
                                          memory_parsimonious=True)
            self.assertArrayAlmostEqual(Up, U)
            I_fidelity = ff.infidelity(pulse, S, omega)
            I_transfer = np.einsum('...ii', U) / d**2
            self.assertArrayAlmostEqual(I_transfer, I_fidelity)

            S = np.outer(1e-2 * (np.arange(n_nops) + 1),
                         400 / (omega**2 + 400))
            U = ff.error_transfer_matrix(pulse, S, omega)
            # Calculate U in loop
            Up = ff.error_transfer_matrix(pulse,
                                          S,
                                          omega,
                                          memory_parsimonious=True)
            self.assertArrayAlmostEqual(Up, U)
            I_fidelity = ff.infidelity(pulse, S, omega)
            I_transfer = np.einsum('...ii', U) / d**2
            self.assertArrayAlmostEqual(I_transfer, I_fidelity)

            S = np.einsum('i,j,o->ijo', 1e-2 * (np.arange(n_nops) + 1),
                          1e-2 * (np.arange(n_nops) + 1),
                          400 / (omega**2 + 400))
            U = ff.error_transfer_matrix(pulse, S, omega)
            # Calculate U in loop
            Up = ff.error_transfer_matrix(pulse,
                                          S,
                                          omega,
                                          memory_parsimonious=True)
            self.assertArrayAlmostEqual(Up, U)
            I_fidelity = ff.infidelity(pulse, S, omega)
            I_transfer = np.einsum('...ii', U) / d**2
            self.assertArrayAlmostEqual(I_transfer, I_fidelity)
    def test_single_qubit_error_transfer_matrix(self):
        """Test the calculation of the single-qubit transfer matrix"""
        d = 2
        for n_dt in rng.integers(1, 11, 10):
            pulse = testutil.rand_pulse_sequence(d, n_dt, 3, 2, btype='Pauli')
            traceless = rng.integers(2, dtype=bool)
            if not traceless:
                # Test that result correct for finite trace n_oper (edge case)
                pulse.n_opers[0] = np.eye(d) / np.sqrt(d)

            omega = util.get_sample_frequencies(pulse, n_samples=51)
            n_oper_identifiers = pulse.n_oper_identifiers
            traces = pulse.basis.four_element_traces.todense()

            # Single spectrum, different spectra for each noise oper, and
            # Cross-correlated spectra which are complex, real part symmetric and
            # imaginary part antisymmetric
            spectra = [
                1e-8 / omega**2,
                np.outer(1e-6 * np.arange(1, 3), 400 / (omega**2 + 400)),
                np.array([[
                    1e-6 / abs(omega), 1e-8 / abs(omega) + 1j * 1e-8 / omega
                ], [1e-8 / abs(omega) - 1j * 1e-8 / omega, 2e-6 / abs(omega)]])
            ]

            for S in spectra:
                # Assert fidelity is same as computed by infidelity()
                U = ff.error_transfer_matrix(pulse, S, omega)
                # Calculate U in loop
                Up = ff.error_transfer_matrix(pulse,
                                              S,
                                              omega,
                                              memory_parsimonious=True)
                # Calculate on foot (multi-qubit way)
                Gamma = numeric.calculate_decay_amplitudes(
                    pulse, S, omega, n_oper_identifiers)
                Delta = numeric.calculate_frequency_shifts(
                    pulse, S, omega, n_oper_identifiers)
                K = -(np.einsum('...kl,klji->...ij', Gamma, traces) -
                      np.einsum('...kl,kjli->...ij', Gamma, traces) -
                      np.einsum('...kl,kilj->...ij', Gamma, traces) +
                      np.einsum('...kl,kijl->...ij', Gamma, traces)) / 2
                U_onfoot = sla.expm(K.sum(tuple(range(K.ndim - 2))))
                U_from_K = ff.error_transfer_matrix(cumulant_function=K)
                self.assertArrayAlmostEqual(Up, U)
                self.assertArrayAlmostEqual(U, U_onfoot, atol=1e-14)
                self.assertArrayAlmostEqual(U_from_K, U_onfoot)
                if traceless:
                    # Simplified fidelity calculation relies on traceless n_opers
                    I_fidelity = ff.infidelity(pulse, S, omega)
                    I_decayamps = -np.einsum('...ii', K) / d**2
                    I_transfer = 1 - np.einsum('...ii', U) / d**2
                    self.assertArrayAlmostEqual(I_fidelity, I_decayamps)
                    self.assertArrayAlmostEqual(I_transfer,
                                                I_fidelity.sum(),
                                                rtol=1e-4,
                                                atol=1e-10)

                # second order
                K -= (np.einsum('...kl,klji->...ij', Delta, traces) -
                      np.einsum('...kl,lkji->...ij', Delta, traces) -
                      np.einsum('...kl,klij->...ij', Delta, traces) +
                      np.einsum('...kl,lkij->...ij', Delta, traces)) / 2
                U = ff.error_transfer_matrix(pulse,
                                             S,
                                             omega,
                                             second_order=True)
                U_onfoot = sla.expm(K.sum(tuple(range(K.ndim - 2))))
                U_from_K = ff.error_transfer_matrix(cumulant_function=K)
                self.assertArrayAlmostEqual(U, U_onfoot, atol=1e-14)
                self.assertArrayAlmostEqual(U_from_K, U_onfoot)
                if traceless:
                    # Simplified fidelity calculation relies on traceless n_opers
                    I_transfer = 1 - np.einsum('...ii', U) / d**2
                    self.assertArrayAlmostEqual(I_transfer,
                                                I_fidelity.sum(),
                                                rtol=1e-4,
                                                atol=1e-10)
    def test_single_qubit_error_transfer_matrix(self):
        """Test the calculation of the single-qubit transfer matrix"""
        d = 2
        for n_dt in rng.randint(1, 11, 10):
            pulse = testutil.rand_pulse_sequence(d, n_dt, 3, 2, btype='Pauli')
            omega = util.get_sample_frequencies(pulse, n_samples=51)
            n_oper_identifiers = pulse.n_oper_identifiers
            traces = pulse.basis.four_element_traces.todense()

            # Single spectrum
            # Assert fidelity is same as computed by infidelity()
            S = 1e-8 / omega**2
            U = ff.error_transfer_matrix(pulse, S, omega)
            # Calculate U in loop
            Up = ff.error_transfer_matrix(pulse,
                                          S,
                                          omega,
                                          memory_parsimonious=True)
            # Calculate on foot (multi-qubit way)
            Gamma = numeric.calculate_decay_amplitudes(pulse, S, omega,
                                                       n_oper_identifiers)
            K = -(np.einsum('...kl,klji->...ij', Gamma, traces) -
                  np.einsum('...kl,kjli->...ij', Gamma, traces) -
                  np.einsum('...kl,kilj->...ij', Gamma, traces) +
                  np.einsum('...kl,kijl->...ij', Gamma, traces)) / 2
            U_onfoot = sla.expm(K.sum(0))
            U_from_K = ff.error_transfer_matrix(cumulant_function=K)
            I_fidelity = ff.infidelity(pulse, S, omega)
            I_decayamps = -np.einsum('...ii', K) / d**2
            I_transfer = 1 - np.einsum('...ii', U) / d**2
            self.assertArrayAlmostEqual(Up, U)
            self.assertArrayAlmostEqual(I_fidelity, I_decayamps)
            self.assertArrayAlmostEqual(I_transfer,
                                        I_fidelity.sum(),
                                        rtol=1e-4)
            self.assertArrayAlmostEqual(U, U_onfoot, atol=1e-14)
            self.assertArrayAlmostEqual(U_from_K, U_onfoot)

            # Different spectra for each noise oper
            S = np.outer(1e-6 * np.arange(1, 3), 400 / (omega**2 + 400))
            U = ff.error_transfer_matrix(pulse, S, omega)
            Up = ff.error_transfer_matrix(pulse,
                                          S,
                                          omega,
                                          memory_parsimonious=True)
            Gamma = numeric.calculate_decay_amplitudes(pulse, S, omega,
                                                       n_oper_identifiers)
            K = -(np.einsum('...kl,klji->...ij', Gamma, traces) -
                  np.einsum('...kl,kjli->...ij', Gamma, traces) -
                  np.einsum('...kl,kilj->...ij', Gamma, traces) +
                  np.einsum('...kl,kijl->...ij', Gamma, traces)) / 2
            U_onfoot = sla.expm(K.sum(0))
            U_from_K = ff.error_transfer_matrix(cumulant_function=K)
            I_fidelity = ff.infidelity(pulse, S, omega)
            I_decayamps = -np.einsum('...ii', K) / d**2
            I_transfer = 1 - np.einsum('...ii', U) / d**2
            self.assertArrayAlmostEqual(Up, U)
            self.assertArrayAlmostEqual(I_fidelity, I_decayamps)
            self.assertArrayAlmostEqual(I_transfer,
                                        I_fidelity.sum(),
                                        rtol=1e-4)
            self.assertArrayAlmostEqual(U, U_onfoot, atol=1e-14)
            self.assertArrayAlmostEqual(U_from_K, U_onfoot)

            # Cross-correlated spectra are complex, real part symmetric and
            # imaginary part antisymmetric
            S = np.array(
                [[1e-6 / abs(omega), 1e-8 / abs(omega) + 1j * 1e-8 / omega],
                 [1e-8 / abs(omega) - 1j * 1e-8 / omega, 2e-6 / abs(omega)]])
            U = ff.error_transfer_matrix(pulse, S, omega)
            Up = ff.error_transfer_matrix(pulse,
                                          S,
                                          omega,
                                          memory_parsimonious=True)
            Gamma = numeric.calculate_decay_amplitudes(pulse, S, omega,
                                                       n_oper_identifiers)
            K = -(np.einsum('...kl,klji->...ij', Gamma, traces) -
                  np.einsum('...kl,kjli->...ij', Gamma, traces) -
                  np.einsum('...kl,kilj->...ij', Gamma, traces) +
                  np.einsum('...kl,kijl->...ij', Gamma, traces)) / 2
            U_onfoot = sla.expm(K.sum((0, 1)))
            U_from_K = ff.error_transfer_matrix(cumulant_function=K)
            I_fidelity = ff.infidelity(pulse, S, omega)
            I_decayamps = -np.einsum('...ii', K) / d**2
            I_transfer = 1 - np.einsum('...ii', U) / d**2
            self.assertArrayAlmostEqual(Up, U)
            self.assertArrayAlmostEqual(I_fidelity, I_decayamps)
            self.assertArrayAlmostEqual(I_transfer,
                                        I_fidelity.sum(),
                                        rtol=1e-4)
            self.assertArrayAlmostEqual(U, U_onfoot, atol=1e-14)
            self.assertArrayAlmostEqual(U_from_K, U_onfoot)
    def test_plot_error_transfer_matrix(self):
        omega = ff.util.get_sample_frequencies(simple_pulse)
        S = 1e-4 * np.sin(omega) / omega

        # Test calling with pulse, spectrum, omega
        fig, grid = plotting.plot_error_transfer_matrix(simple_pulse,
                                                        S,
                                                        omega,
                                                        colorscale='linear')
        fig, grid = plotting.plot_error_transfer_matrix(simple_pulse,
                                                        S,
                                                        omega,
                                                        fig=fig)
        fig, grid = plotting.plot_error_transfer_matrix(simple_pulse,
                                                        S,
                                                        omega,
                                                        grid=grid)

        # Test calling with precomputed transfer matrix
        U = ff.error_transfer_matrix(simple_pulse, S, omega)
        fig, grid = plotting.plot_error_transfer_matrix(U=U)

        # Test calling with precomputed transfer matrix and pulse
        U = ff.error_transfer_matrix(simple_pulse, S, omega)
        fig, grid = plotting.plot_error_transfer_matrix(simple_pulse, U=U)

        # Test calling with precomputed transfer matrix of ndim == 2
        U = ff.error_transfer_matrix(simple_pulse, S, omega)
        fig, grid = plotting.plot_error_transfer_matrix(U=U[0])

        # Log colorscale
        fig, grid = plotting.plot_error_transfer_matrix(U=U, colorscale='log')

        # Non-default args
        n_oper_inds = sample(range(len(complicated_pulse.n_opers)),
                             rng.randint(2, 4))
        n_oper_identifiers = complicated_pulse.n_oper_identifiers[n_oper_inds]

        basis_labels = []
        for i in range(4):
            basis_labels.append(string.ascii_uppercase[rng.randint(0, 26)])

        omega = ff.util.get_sample_frequencies(complicated_pulse,
                                               n_samples=50,
                                               spacing='log')
        S = np.exp(-omega**2)
        U = ff.error_transfer_matrix(complicated_pulse, S, omega)
        fig, grid = plotting.plot_error_transfer_matrix(
            complicated_pulse,
            S=S,
            omega=omega,
            n_oper_identifiers=n_oper_identifiers,
            basis_labels=basis_labels,
            basis_labelsize=4,
            linthresh=1e-4,
            cmap=plt.cm.jet)
        fig, grid = plotting.plot_error_transfer_matrix(
            U=U[n_oper_inds],
            n_oper_identifiers=n_oper_identifiers,
            basis_labels=basis_labels,
            basis_labelsize=4,
            linthresh=1e-4,
            cmap=plt.cm.jet)

        # neither U nor all of pulse, S, omega given
        with self.assertRaises(ValueError):
            plotting.plot_error_transfer_matrix(complicated_pulse, S)

        # invalid identifiers
        with self.assertRaises(ValueError):
            plotting.plot_error_transfer_matrix(complicated_pulse,
                                                S,
                                                omega,
                                                n_oper_identifiers=['foo'])

        # number of basis_labels not correct
        with self.assertRaises(ValueError):
            plotting.plot_error_transfer_matrix(complicated_pulse,
                                                S,
                                                omega,
                                                basis_labels=basis_labels[:2])

        # grid too small
        with self.assertRaises(ValueError):
            plotting.plot_error_transfer_matrix(complicated_pulse,
                                                S,
                                                omega,
                                                grid=grid[:1])

        # Test various keyword args for matplotlib for the two-qubit pulse
        S = np.tile(S, (6, 6, 1))
        grid_kw = {'axes_pad': 0.1}
        imshow_kw = {'interpolation': 'bilinear'}
        figure_kw = {'num': 1}
        fig, ax = plotting.plot_error_transfer_matrix(two_qubit_pulse,
                                                      S,
                                                      omega,
                                                      imshow_kw=imshow_kw,
                                                      grid_kw=grid_kw,
                                                      **figure_kw)

        plt.close('all')
    def test_single_qubit_error_transfer_matrix(self):
        """Test the calculation of the single-qubit transfer matrix"""
        d = 2
        for n_dt in rng.randint(1, 11, 10):
            pulse = testutil.rand_pulse_sequence(d, n_dt, 3, 2, btype='Pauli')
            omega = ff.util.get_sample_frequencies(pulse, n_samples=51)
            n_oper_identifiers = pulse.n_oper_identifiers
            traces = pulse.basis.four_element_traces.todense()

            # Single spectrum
            # Assert fidelity is same as computed by infidelity()
            S = 1e-2 / omega**2
            U = ff.error_transfer_matrix(pulse, S, omega)
            # Calculate U in loop
            Up = ff.error_transfer_matrix(pulse,
                                          S,
                                          omega,
                                          memory_parsimonious=True)
            self.assertArrayAlmostEqual(Up, U)
            I_fidelity = ff.infidelity(pulse, S, omega)
            I_transfer = np.einsum('...ii', U) / d**2
            self.assertArrayAlmostEqual(I_transfer, I_fidelity)

            # Check that _single_qubit_error_transfer_matrix and
            # _multi_qubit_... # give the same
            u_kl = numeric.calculate_error_vector_correlation_functions(
                pulse, S, omega, n_oper_identifiers)
            U_multi = (np.einsum('...kl,klij->...ij', u_kl, traces) / 2 +
                       np.einsum('...kl,klji->...ij', u_kl, traces) / 2 -
                       np.einsum('...kl,kilj->...ij', u_kl, traces))
            self.assertArrayAlmostEqual(U, U_multi, atol=1e-14)

            # Different spectra for each noise oper
            S = np.outer(1e-2 * np.arange(1, 3), 400 / (omega**2 + 400))
            U = ff.error_transfer_matrix(pulse, S, omega)
            # Calculate U in loop
            Up = ff.error_transfer_matrix(pulse,
                                          S,
                                          omega,
                                          memory_parsimonious=True)
            self.assertArrayAlmostEqual(Up, U)
            I_fidelity = ff.infidelity(pulse, S, omega)
            I_transfer = np.einsum('...ii', U) / d**2
            self.assertArrayAlmostEqual(I_transfer, I_fidelity)

            # Check that _single_qubit_error_transfer_matrix and
            # _multi_qubit_... # give the same
            u_kl = numeric.calculate_error_vector_correlation_functions(
                pulse, S, omega, n_oper_identifiers)
            U_multi = (np.einsum('...kl,klij->...ij', u_kl, traces) / 2 +
                       np.einsum('...kl,klji->...ij', u_kl, traces) / 2 -
                       np.einsum('...kl,kilj->...ij', u_kl, traces))
            self.assertArrayAlmostEqual(U, U_multi, atol=1e-14)

            # Cross-correlated spectra
            S = np.einsum('i,j,o->ijo',
                          1e-2 * np.arange(1, 3),
                          1e-2 * np.arange(1, 3),
                          400 / (omega**2 + 400),
                          dtype=complex)
            # Cross spectra are complex
            S[0, 1] *= 1 + 1j
            S[1, 0] *= 1 - 1j
            U = ff.error_transfer_matrix(pulse, S, omega)
            # Calculate U in loop
            Up = ff.error_transfer_matrix(pulse,
                                          S,
                                          omega,
                                          memory_parsimonious=True)
            self.assertArrayAlmostEqual(Up, U)
            I_fidelity = ff.infidelity(pulse, S, omega)
            I_transfer = np.einsum('...ii', U) / d**2
            self.assertArrayAlmostEqual(I_transfer, I_fidelity)

            # Check that _single_qubit_error_transfer_matrix and
            # _multi_qubit_... # give the same
            u_kl = numeric.calculate_error_vector_correlation_functions(
                pulse, S, omega, n_oper_identifiers)
            U_multi = np.zeros_like(U)
            U_multi = (np.einsum('...kl,klij->...ij', u_kl, traces) / 2 +
                       np.einsum('...kl,klji->...ij', u_kl, traces) / 2 -
                       np.einsum('...kl,kilj->...ij', u_kl, traces))
            self.assertArrayAlmostEqual(U, U_multi, atol=1e-16)