def test_get_sample_frequencies(self): pulse = PulseSequence([[util.paulis[1], [np.pi / 2]]], [[util.paulis[1], [1]]], [abs(rng.standard_normal())]) # Default args omega = util.get_sample_frequencies(pulse) self.assertAlmostEqual(omega[0], 2e-2 * np.pi / pulse.tau) self.assertAlmostEqual(omega[-1], 2e2 * np.pi / pulse.tau) self.assertEqual(len(omega), 300) self.assertTrue((omega >= 0).all()) self.assertLessEqual(np.var(np.diff(np.log(omega[150:]))), 1e-16) # custom args omega = util.get_sample_frequencies(pulse, spacing='linear', n_samples=50, include_quasistatic=True) self.assertAlmostEqual(omega[0], 0) self.assertAlmostEqual(omega[-1], 2e2 * np.pi / pulse.tau) self.assertEqual(len(omega), 50) self.assertTrue((omega >= 0).all()) self.assertLessEqual(np.var(np.diff(omega[1:])), 1e-16) # Exceptions with self.assertRaises(ValueError): omega = util.get_sample_frequencies(pulse, spacing='foo')
def test_integration(self): """Compare integrals to numerical results.""" d = 3 pulse = testutil.rand_pulse_sequence(d, 5) # including zero E = util.get_sample_frequencies(pulse, 51, include_quasistatic=True) for i, (eigval, dt, t) in enumerate(zip(pulse.eigvals, pulse.dt, pulse.t)): integral, integral_numeric = _get_integrals_first_order( d, E, eigval, dt, t) self.assertArrayAlmostEqual(integral, integral_numeric, atol=1e-4) integral, integral_numeric = _get_integrals_second_order( d, E, eigval, dt, t) self.assertArrayAlmostEqual(integral, integral_numeric, atol=1e-4) # excluding (most likely) zero E = testutil.rng.standard_normal(51) for i, (eigval, dt, t) in enumerate(zip(pulse.eigvals, pulse.dt, pulse.t)): integral, integral_numeric = _get_integrals_first_order( d, E, eigval, dt, t) self.assertArrayAlmostEqual(integral, integral_numeric, atol=1e-4) integral, integral_numeric = _get_integrals_second_order( d, E, eigval, dt, t) self.assertArrayAlmostEqual(integral, integral_numeric, atol=1e-4)
def test_second_order_filter_function(self): for d, n_nops in zip(rng.integers(2, 7, 5), rng.integers(1, 5, 5)): pulse = testutil.rand_pulse_sequence(d, 3, 2, n_nops) omega = util.get_sample_frequencies(pulse, n_samples=42) # Make sure result is the same with or without intermediates pulse.cache_control_matrix(omega, cache_intermediates=True) F = pulse.get_filter_function(omega, order=1) F_1 = pulse.get_filter_function(omega, order=2) # Test caching F_2 = pulse.get_filter_function(omega, order=2) F_3 = numeric.calculate_second_order_filter_function( pulse.eigvals, pulse.eigvecs, pulse.propagators, omega, pulse.basis, pulse.n_opers, pulse.n_coeffs, pulse.dt, show_progressbar=False, intermediates=None) # Make sure first and second order are of same order of magnitude rel = np.linalg.norm(F) / np.linalg.norm(F_1) self.assertIs(F_1, F_2) self.assertArrayEqual(F_1, F_3) self.assertEqual(F_1.shape, (n_nops, n_nops, d**2, d**2, 42)) self.assertLessEqual(rel, 10) self.assertGreaterEqual(rel, 1 / 10)
def test_SE(self): """Spin echo""" tau = np.pi tau_pi = 1e-8 n = 1 H_c, dt = testutil.generate_dd_hamiltonian(n, tau=tau, tau_pi=tau_pi, dd_type='cpmg') H_n = [[util.paulis[3] / 2, np.ones_like(dt)]] SE_pulse = ff.PulseSequence(H_c, H_n, dt) omega = util.get_sample_frequencies(SE_pulse, 100, spacing='linear') # Comparison to filter function defined with omega**2 F = SE_pulse.get_filter_function(omega)[0, 0] * omega**2 self.assertArrayAlmostEqual(F, analytic.SE(omega * tau), atol=1e-10) # Test again with a factor of one between the noise operators and # coefficients r = rng.standard_normal() H_n = [[util.paulis[3] / 2 * r, np.ones_like(dt) / r]] SE_pulse = ff.PulseSequence(H_c, H_n, dt) # Comparison to filter function defined with omega**2 F = SE_pulse.get_filter_function(omega)[0, 0] * omega**2 self.assertArrayAlmostEqual(F, analytic.SE(omega * tau), atol=1e-10)
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_FID(self): """FID""" tau = abs(rng.standard_normal()) FID_pulse = ff.PulseSequence([[util.paulis[1] / 2, [0]]], [[util.paulis[3] / 2, [1]]], [tau]) omega = util.get_sample_frequencies(FID_pulse, 50, spacing='linear') # Comparison to filter function defined with omega**2 F = FID_pulse.get_filter_function(omega).squeeze() * omega**2 self.assertArrayAlmostEqual(F, analytic.FID(omega * tau), atol=1e-10)
def test_symmetrize_spectrum(self): pulse = PulseSequence( [[util.paulis[1], [np.pi/2]]], [[util.paulis[1], [1]]], [abs(rng.standard_normal())] ) asym_omega = util.get_sample_frequencies(pulse, symmetric=False, n_samples=100) sym_omega = util.get_sample_frequencies(pulse, symmetric=True, n_samples=200) S_symmetrized, omega_symmetrized = util.symmetrize_spectrum( 1/asym_omega**0.7, asym_omega) self.assertArrayEqual(omega_symmetrized, sym_omega) self.assertArrayEqual(S_symmetrized[99::-1], S_symmetrized[100:]) self.assertArrayEqual(S_symmetrized[100:]*2, 1/asym_omega**0.7) # zero frequency not doubled omega = np.arange(10) S_sym, omega_sym = util.symmetrize_spectrum(omega, omega) self.assertArrayEqual(S_sym, np.abs(np.arange(-9, 10)/2)) self.assertArrayEqual(omega_sym, np.arange(-9, 10))
def test_caching(self): pauli_pulse = testutil.rand_pulse_sequence(4, 1, 1, 4, 'Pauli') ggm_pulse = testutil.rand_pulse_sequence(4, 1, 1, 4, 'GGM') attrs = ('omega', 'eigvals', 'eigvecs', 'propagators', 'total_phases', 'total_propagator', 'filter_function', 'total_propagator_liouville', 'control_matrix') pauli_pulse.cleanup('all') remapped_pauli_pulse = ff.remap(pauli_pulse, (1, 0)) for attr in attrs: self.assertEqual(pauli_pulse.is_cached(attr), remapped_pauli_pulse.is_cached(attr)) omega = util.get_sample_frequencies(pauli_pulse, n_samples=50) pauli_pulse.cache_filter_function(omega) remapped_pauli_pulse = ff.remap(pauli_pulse, (1, 0)) for attr in attrs: self.assertEqual(pauli_pulse.is_cached(attr), remapped_pauli_pulse.is_cached(attr)) ggm_pulse.cleanup('all') remapped_ggm_pulse = ff.remap(ggm_pulse, (1, 0)) for attr in attrs: self.assertEqual(ggm_pulse.is_cached(attr), remapped_ggm_pulse.is_cached(attr)) omega = util.get_sample_frequencies(ggm_pulse, n_samples=50) ggm_pulse.cache_filter_function(omega) with self.assertWarns(UserWarning): remapped_ggm_pulse = ff.remap(ggm_pulse, (1, 0)) for attr in attrs[:-2]: self.assertEqual(ggm_pulse.is_cached(attr), remapped_ggm_pulse.is_cached(attr)) for attr in attrs[-2:]: self.assertFalse(remapped_ggm_pulse.is_cached(attr))
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_cache_intermediates(self): """Test caching of intermediate elements""" pulse = testutil.rand_pulse_sequence(3, 4, 2, 3) omega = util.get_sample_frequencies(pulse, 33, spacing='linear') ctrlmat = pulse.get_control_matrix(omega, cache_intermediates=True) filtfun = pulse.get_filter_function(omega, cache_intermediates=True) self.assertIsNotNone(pulse._intermediates) self.assertArrayAlmostEqual(pulse._intermediates['control_matrix_step'].sum(0), ctrlmat) self.assertArrayAlmostEqual(numeric.calculate_filter_function(ctrlmat), filtfun) self.assertArrayAlmostEqual(pulse._intermediates['n_opers_transformed'], numeric._transform_noise_operators(pulse.n_coeffs, pulse.n_opers, pulse.eigvecs)) eigvecs_prop = numeric._propagate_eigenvectors(pulse.propagators[:-1], pulse.eigvecs) basis_transformed = np.einsum('gba,kbc,gcd->gkad', eigvecs_prop.conj(), pulse.basis, eigvecs_prop) self.assertArrayAlmostEqual(pulse._intermediates['basis_transformed'], basis_transformed, atol=1e-14)
def test_6_pulse_CPMG(self): """6-pulse CPMG""" tau = np.pi tau_pi = 1e-9 n = 6 H_c, dt = testutil.generate_dd_hamiltonian(n, tau=tau, tau_pi=tau_pi, dd_type='cpmg') H_n = [[util.paulis[3] / 2, np.ones_like(dt)]] CPMG_pulse = ff.PulseSequence(H_c, H_n, dt) omega = util.get_sample_frequencies(CPMG_pulse, 100, spacing='log') # Comparison to filter function defined with omega**2 F = CPMG_pulse.get_filter_function(omega)[0, 0] * omega**2 self.assertArrayAlmostEqual(F, analytic.CPMG(omega * tau, n), atol=1e-10)
def test_filter_functions(self): """Filter functions equal for different bases""" # Set up random Hamiltonian base_pulse = testutil.rand_pulse_sequence(4, 3, 1, 1) omega = util.get_sample_frequencies(base_pulse, n_samples=200) pauli_basis = ff.Basis.pauli(2) ggm_basis = ff.Basis.ggm(4) from_random_basis = ff.Basis.from_partial(base_pulse.n_opers) bases = (pauli_basis, ggm_basis, from_random_basis) # Get Pulses with different bases F = [] for b in bases: pulse = copy(base_pulse) pulse.basis = b F.append(pulse.get_filter_function(omega).sum(0)) for pair in product(F, F): self.assertArrayAlmostEqual(*pair)
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_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_accuracy(self): paulis = np.array(util.paulis) I, X, Y, Z = paulis amps = rng.standard_normal(rng.randint(1, 11)) pulse = ff.PulseSequence( [[util.tensor(X, Y, Z), amps]], [[util.tensor(X, I, I), np.ones_like(amps), 'XII'], [util.tensor(I, X, I), np.ones_like(amps), 'IXI'], [util.tensor(I, I, X), np.ones_like(amps), 'IIX']], np.ones_like(amps), ff.Basis.pauli(3) ) omega = util.get_sample_frequencies(pulse, 50) pulse.cache_filter_function(omega) for _ in range(100): order = rng.permutation(range(3)) reordered_pulse = ff.PulseSequence( [[util.tensor(*paulis[1:][order]), amps]], [[util.tensor(*paulis[[1, 0, 0]][order]), np.ones_like(amps), (''.join(['XII'[o] for o in order]))], [util.tensor(*paulis[[0, 1, 0]][order]), np.ones_like(amps), (''.join(['IXI'[o] for o in order]))], [util.tensor(*paulis[[0, 0, 1]][order]), np.ones_like(amps), (''.join(['IIX'[o] for o in order]))]], np.ones_like(amps), ff.Basis.pauli(3) ) reordered_pulse.cache_filter_function(omega) remapped_pulse = ff.remap( pulse, order, oper_identifier_mapping={ 'A_0': 'A_0', 'XII': ''.join(['XII'[o] for o in order]), 'IXI': ''.join(['IXI'[o] for o in order]), 'IIX': ''.join(['IIX'[o] for o in order]) } ) self.assertEqual(reordered_pulse, remapped_pulse) self.assertArrayAlmostEqual(reordered_pulse.t, remapped_pulse.t) self.assertEqual(reordered_pulse.d, remapped_pulse.d) self.assertEqual(reordered_pulse.basis, remapped_pulse.basis) self.assertArrayAlmostEqual(reordered_pulse._omega, remapped_pulse.omega) self.assertArrayAlmostEqual(reordered_pulse._propagators, remapped_pulse._propagators, atol=1e-14) self.assertArrayAlmostEqual(reordered_pulse._total_propagator, remapped_pulse._total_propagator, atol=1e-14) self.assertArrayAlmostEqual( reordered_pulse._total_propagator_liouville, remapped_pulse._total_propagator_liouville, atol=1e-14 ) self.assertArrayAlmostEqual(reordered_pulse._total_phases, remapped_pulse._total_phases) self.assertArrayAlmostEqual(reordered_pulse._control_matrix, remapped_pulse._control_matrix, atol=1e-12) self.assertArrayAlmostEqual(reordered_pulse._filter_function, remapped_pulse._filter_function, atol=1e-12) # Test the eigenvalues and -vectors by the characteristic equation self.assertCorrectDiagonalization(remapped_pulse, atol=1e-14)
def test_filter_function(self): """Test the filter function calculation and related methods""" for d, n_dt in zip(rng.randint(2, 10, (3,)), rng.randint(10, 200, (3,))): total_pulse = testutil.rand_pulse_sequence(d, n_dt, 4, 6) c_opers, c_coeffs = total_pulse.c_opers, total_pulse.c_coeffs n_opers, n_coeffs = total_pulse.n_opers, total_pulse.n_coeffs dt = total_pulse.dt total_eigvals, total_eigvecs, _ = numeric.diagonalize( np.einsum('il,ijk->ljk', c_coeffs, c_opers), total_pulse.dt ) omega = util.get_sample_frequencies(total_pulse, n_samples=100) # Try the progress bar control_matrix = total_pulse.get_control_matrix( omega, show_progressbar=True) # Check that some attributes are cached self.assertIsNotNone(total_pulse._total_phases) self.assertIsNotNone(total_pulse._total_propagator) self.assertIsNotNone(total_pulse._total_propagator_liouville) # Calculate everything 'on foot' pulses = [ff.PulseSequence(list(zip(c_opers, c_coeffs[:, i:i+1])), list(zip(n_opers, n_coeffs[:, i:i+1])), dt[i:i+1]) for i in range(n_dt)] phases = np.empty((n_dt, len(omega)), dtype=complex) L = np.empty((n_dt, d**2, d**2)) control_matrix_g = np.empty((n_dt, 6, d**2, len(omega)), dtype=complex) for g, pulse in enumerate(pulses): phases[g] = np.exp(1j*total_pulse.t[g]*omega) L[g] = ff.superoperator.liouville_representation(total_pulse.propagators[g], total_pulse.basis) control_matrix_g[g] = pulse.get_control_matrix(omega) # Check that both methods of calculating the control are the same control_matrix_from_atomic = numeric.calculate_control_matrix_from_atomic( phases, control_matrix_g, L ) control_matrix_from_scratch = numeric.calculate_control_matrix_from_scratch( eigvals=total_eigvals, eigvecs=total_eigvecs, propagators=total_pulse.propagators, omega=omega, basis=total_pulse.basis, n_opers=n_opers, n_coeffs=n_coeffs, dt=total_pulse.dt ) self.assertArrayAlmostEqual(control_matrix, control_matrix_from_scratch) # first column (identity element) always zero but susceptible to # floating point error, increase atol self.assertArrayAlmostEqual(control_matrix_from_scratch, control_matrix_from_atomic, atol=1e-13) # Check if the filter functions for autocorrelated noise are real filter_function = total_pulse.get_filter_function(omega) self.assertTrue(np.isreal( filter_function[np.eye(len(n_opers), dtype=bool)] ).all()) # Check switch between fidelity and generalized filter function F_generalized = total_pulse.get_filter_function( omega, which='generalized') F_fidelity = total_pulse.get_filter_function( omega, which='fidelity') # Check that F_fidelity is correctly reduced from F_generalized self.assertArrayAlmostEqual(F_fidelity, F_generalized.trace(axis1=2, axis2=3)) # Hit getters again to check caching functionality F_generalized = total_pulse.get_filter_function( omega, which='generalized') F_fidelity = total_pulse.get_filter_function( omega, which='fidelity') # Check that F_fidelity is correctly reduced from F_generalized self.assertArrayAlmostEqual(F_fidelity, F_generalized.trace(axis1=2, axis2=3)) # Different set of frequencies than cached F_generalized = total_pulse.get_filter_function( omega + 1, which='generalized') F_fidelity = total_pulse.get_filter_function( omega + 1, which='fidelity') # Check that F_fidelity is correctly reduced from F_generalized self.assertArrayAlmostEqual(F_fidelity, F_generalized.trace(axis1=2, axis2=3))
def test_pulse_sequence_attributes(self): """Test attributes of single instance""" X, Y, Z = util.paulis[1:] n_dt = rng.randint(1, 10) # trivial case A = ff.PulseSequence([[X, rng.standard_normal(n_dt), 'X']], [[Z, rng.standard_normal(n_dt), 'Z']], np.abs(rng.standard_normal(n_dt))) self.assertFalse(A == 1) self.assertTrue(A != 1) # different number of time steps B = ff.PulseSequence([[X, rng.standard_normal(n_dt+1), 'X']], [[Z, rng.standard_normal(n_dt+1), 'Z']], np.abs(rng.standard_normal(n_dt+1))) self.assertFalse(A == B) self.assertTrue(A != B) # different time steps B = ff.PulseSequence( list(zip(A.c_opers, A.c_coeffs, A.c_oper_identifiers)), list(zip(A.n_opers, A.n_coeffs, A.n_oper_identifiers)), np.abs(rng.standard_normal(n_dt)) ) self.assertFalse(A == B) self.assertTrue(A != B) # different control opers B = ff.PulseSequence( list(zip([Y], A.c_coeffs, A.c_oper_identifiers)), list(zip(A.n_opers, A.n_coeffs, A.n_oper_identifiers)), A.dt ) self.assertFalse(A == B) self.assertTrue(A != B) # different control coeffs B = ff.PulseSequence( list(zip(A.c_opers, [rng.standard_normal(n_dt)], A.c_oper_identifiers)), list(zip(A.n_opers, A.n_coeffs, A.n_oper_identifiers)), A.dt ) self.assertFalse(A == B) self.assertTrue(A != B) # different noise opers B = ff.PulseSequence( list(zip(A.c_opers, A.c_coeffs, A.c_oper_identifiers)), list(zip([Y], A.n_coeffs, A.n_oper_identifiers)), A.dt ) self.assertFalse(A == B) self.assertTrue(A != B) # different noise coeffs B = ff.PulseSequence( list(zip(A.c_opers, A.c_coeffs, A.c_oper_identifiers)), list(zip(A.n_opers, [rng.standard_normal(n_dt)], A.n_oper_identifiers)), A.dt ) self.assertFalse(A == B) self.assertTrue(A != B) # different control oper identifiers B = ff.PulseSequence( list(zip(A.c_opers, A.c_coeffs, ['foobar'])), list(zip(A.n_opers, A.n_coeffs, A.n_oper_identifiers)), A.dt ) self.assertFalse(A == B) self.assertTrue(A != B) # different noise oper identifiers B = ff.PulseSequence( list(zip(A.c_opers, A.c_coeffs, A.c_oper_identifiers)), list(zip(A.n_opers, A.n_coeffs, ['foobar'])), A.dt ) self.assertFalse(A == B) self.assertTrue(A != B) # different bases elem = testutil.rand_herm_traceless(2) B = ff.PulseSequence( list(zip(A.c_opers, A.c_coeffs, A.c_oper_identifiers)), list(zip(A.n_opers, A.n_coeffs, A.n_oper_identifiers)), A.dt, ff.Basis(elem) ) self.assertFalse(A == B) self.assertTrue(A != B) # Test sparse operators for whatever reason A = ff.PulseSequence([[util.paulis[1], [1]]], [[sparse.COO.from_numpy(util.paulis[2]), [2]]], [3]) B = ff.PulseSequence([[sparse.COO.from_numpy(util.paulis[1]), [1]]], [[util.paulis[2], [2]]], [3]) self.assertEqual(A, B) # Test for attributes for attr in A.__dict__.keys(): if not (attr.startswith('_') or '_' + attr in A.__dict__.keys()): # not a cached attribute with self.assertRaises(AttributeError): _ = A.is_cached(attr) else: # set mock attribute at random if rng.randint(0, 2): setattr(A, attr, 'foo') assertion = self.assertTrue else: setattr(A, attr, None) assertion = self.assertFalse assertion(A.is_cached(attr)) # Diagonalization attributes A.diagonalize() self.assertIsNotNone(A.eigvals) self.assertIsNotNone(A.eigvecs) self.assertIsNotNone(A.propagators) A.cleanup('conservative') self.assertIsNotNone(A.eigvals) A.cleanup('conservative') self.assertIsNotNone(A.eigvecs) A.cleanup('conservative') self.assertIsNotNone(A.propagators) aliases = {'eigenvalues': '_eigvals', 'eigenvectors': '_eigvecs', 'total propagator': '_total_propagator', 'total propagator liouville': '_total_propagator_liouville', 'frequencies': '_omega', 'total phases': '_total_phases', 'filter function': '_filter_function', 'fidelity filter function': '_filter_function', 'generalized filter function': '_filter_function_gen', 'pulse correlation filter function': '_filter_function_pc', 'fidelity pulse correlation filter function': '_filter_function_pc', 'generalized pulse correlation filter function': '_filter_function_pc_gen', 'control matrix': '_control_matrix', 'pulse correlation control matrix': '_control_matrix_pc'} for alias, attr in aliases.items(): # set mock attribute at random if rng.randint(0, 2): setattr(A, attr, 'foo') assertion = self.assertTrue else: setattr(A, attr, None) assertion = self.assertFalse assertion(A.is_cached(alias)) assertion(A.is_cached(alias.upper())) assertion(A.is_cached(alias.replace(' ', '_'))) A.cleanup('all') # Test cleanup C = ff.concatenate((A, A), calc_pulse_correlation_FF=True, which='generalized', omega=util.get_sample_frequencies(A)) C.diagonalize() attrs = ['_eigvals', '_eigvecs', '_propagators'] for attr in attrs: self.assertIsNotNone(getattr(C, attr)) C.cleanup() for attr in attrs: self.assertIsNone(getattr(C, attr)) C.diagonalize() C.cache_control_matrix(A.omega) attrs.extend(['_control_matrix', '_total_phases', '_total_propagator', '_total_propagator_liouville']) for attr in attrs: self.assertIsNotNone(getattr(C, attr)) C.cleanup('greedy') for attr in attrs: self.assertIsNone(getattr(C, attr)) C.cache_filter_function(A.omega, which='generalized') for attr in attrs + ['omega', '_filter_function_gen', '_filter_function_pc_gen']: self.assertIsNotNone(getattr(C, attr)) C = ff.concatenate((A, A), calc_pulse_correlation_FF=True, which='fidelity', omega=A.omega) C.diagonalize() C.cache_filter_function(A.omega, which='fidelity') attrs.extend(['omega', '_filter_function', '_filter_function_pc']) for attr in attrs: self.assertIsNotNone(getattr(C, attr)) C.cleanup('all') for attr in attrs + ['_filter_function_gen', '_filter_function_pc_gen']: self.assertIsNone(getattr(C, attr)) C.cache_filter_function(A.omega, which='fidelity') C.cleanup('frequency dependent') freq_attrs = {'omega', '_control_matrix', '_filter_function', '_filter_function_gen', '_filter_function_pc', '_filter_function_pc_gen', '_total_phases'} for attr in freq_attrs: self.assertIsNone(getattr(C, attr)) for attr in set(attrs).difference(freq_attrs): self.assertIsNotNone(getattr(C, attr))
def test_extend_with_identity(self): """Test extending a pulse to more qubits""" ID, X, Y, Z = util.paulis n_dt = 10 coeffs = rng.standard_normal((3, n_dt)) ids = ['X', 'Y', 'Z'] pulse = ff.PulseSequence( list(zip((X, Y, Z), coeffs, ids)), list(zip((X, Y, Z), np.ones((3, n_dt)), ids)), np.ones(n_dt), basis=ff.Basis.pauli(1) ) omega = util.get_sample_frequencies(pulse, spacing='log', n_samples=50) for N in rng.randint(2, 5, 4): for target in rng.randint(0, N-1, 2): pulse.cleanup('all') ext_opers = util.tensor(*np.insert(np.tile(ID, (N-1, 3, 1, 1)), target, (X, Y, Z), axis=0)) # By default, extend should add the target qubit as suffix to # identifiers ext_ids = [i + f'_{target}' for i in ids] ext_pulse = ff.PulseSequence( list(zip(ext_opers, coeffs, ext_ids)), list(zip(ext_opers, np.ones((3, n_dt)), ext_ids)), np.ones(n_dt), basis=ff.Basis.pauli(N) ) # Use custom mapping for identifiers and or labels letters = rng.choice(list(string.ascii_letters), size=(3, 5)) mapped_ids = np.array([''.join(l) for l in letters]) mapping = {i: new_id for i, new_id in zip(ids, mapped_ids)} ext_pulse_mapped_identifiers = ff.PulseSequence( list(zip(ext_opers, coeffs, mapped_ids, ext_ids)), list(zip(ext_opers, np.ones((3, n_dt)), mapped_ids, ext_ids)), np.ones(n_dt), basis=ff.Basis.pauli(N) ) ext_pulse_mapped_labels = ff.PulseSequence( list(zip(ext_opers, coeffs, ext_ids, mapped_ids)), list(zip(ext_opers, np.ones((3, n_dt)), ext_ids, mapped_ids)), np.ones(n_dt), basis=ff.Basis.pauli(N) ) ext_pulse_mapped_identifiers_labels = ff.PulseSequence( list(zip(ext_opers, coeffs, mapped_ids)), list(zip(ext_opers, np.ones((3, n_dt)), mapped_ids)), np.ones(n_dt), basis=ff.Basis.pauli(N) ) calc_filter_functionF = rng.randint(0, 2) if calc_filter_functionF: # Expect things to be cached in extended pulse if original # also was cached pulse.cache_filter_function(omega) ext_pulse.cache_filter_function(omega) test_ext_pulse = ff.extend([(pulse, target)], N, d_per_qubit=2) test_ext_pulse_mapped_identifiers = ff.extend( [(pulse, target, mapping)], N, d_per_qubit=2 ) test_ext_pulse_mapped_labels = ff.extend( [(pulse, target, None, mapping)], N, d_per_qubit=2 ) test_ext_pulse_mapped_identifiers_labels = ff.extend( [(pulse, target, mapping, mapping)], N, d_per_qubit=2 ) self.assertEqual(ext_pulse, test_ext_pulse) self.assertEqual(ext_pulse_mapped_identifiers, test_ext_pulse_mapped_identifiers) self.assertEqual(ext_pulse_mapped_labels, test_ext_pulse_mapped_labels) self.assertEqual(ext_pulse_mapped_identifiers_labels, test_ext_pulse_mapped_identifiers_labels) if calc_filter_functionF: self.assertCorrectDiagonalization(test_ext_pulse, atol=1e-14) self.assertArrayAlmostEqual(test_ext_pulse._propagators, ext_pulse._propagators, atol=1e-14) self.assertArrayAlmostEqual( test_ext_pulse._total_propagator_liouville, ext_pulse._total_propagator_liouville, atol=1e-14 ) self.assertArrayAlmostEqual( test_ext_pulse._total_propagator, ext_pulse._total_propagator, atol=1e-14 ) self.assertArrayAlmostEqual(test_ext_pulse._total_phases, ext_pulse._total_phases) self.assertArrayAlmostEqual(test_ext_pulse._control_matrix, ext_pulse._control_matrix, atol=1e-12) self.assertArrayAlmostEqual( test_ext_pulse._filter_function, ext_pulse._filter_function, atol=1e-12 ) else: self.assertIsNone(test_ext_pulse._eigvals) self.assertIsNone(test_ext_pulse._eigvecs) self.assertIsNone(test_ext_pulse._propagators) self.assertIsNone( test_ext_pulse._total_propagator_liouville) self.assertIsNone(test_ext_pulse._total_propagator) self.assertIsNone(test_ext_pulse._total_phases) self.assertIsNone(test_ext_pulse._control_matrix) self.assertIsNone(test_ext_pulse._filter_function) pulse.cleanup('all') ext_pulse.cleanup('all')
def test_cumulant_function(self): pulse = testutil.rand_pulse_sequence(2, 1, 1, 1) omega = rng.standard_normal(43) spectrum = rng.standard_normal(43) Gamma = numeric.calculate_decay_amplitudes(pulse, spectrum, omega) Delta = numeric.calculate_frequency_shifts(pulse, spectrum, omega) K_1 = numeric.calculate_cumulant_function(pulse, spectrum, omega) K_2 = numeric.calculate_cumulant_function(pulse, decay_amplitudes=Gamma) K_3 = numeric.calculate_cumulant_function(pulse, spectrum, omega, second_order=True) K_4 = numeric.calculate_cumulant_function(pulse, decay_amplitudes=Gamma, frequency_shifts=Delta, second_order=True) self.assertArrayAlmostEqual(K_1, K_2) self.assertArrayAlmostEqual(K_3, K_4) with self.assertRaises(ValueError): # Neither spectrum + frequencies nor decay amplitudes supplied numeric.calculate_cumulant_function(pulse, None, None, decay_amplitudes=None) with self.assertRaises(ValueError): # Neither spectrum + frequencies nor frequency shifts supplied numeric.calculate_cumulant_function(pulse, None, None, frequency_shifts=None, second_order=True) with self.assertRaises(ValueError): # Trying to get correlation cumulant function for second order numeric.calculate_cumulant_function(pulse, spectrum, omega, second_order=True, which='correlations') with self.assertRaises(ValueError): # Using precomputed frequency shifts or decay amplitudes but different shapes numeric.calculate_cumulant_function(pulse, spectrum, omega, second_order=True, decay_amplitudes=Gamma[1:]) with self.assertWarns(UserWarning): # Memory parsimonious only works for decay amplitudes numeric.calculate_cumulant_function(pulse, spectrum, omega, second_order=True, memory_parsimonious=True) for d in [2, *rng.integers(2, 7, 5)]: pulse = testutil.rand_pulse_sequence(d, 3, 2, 2) omega = util.get_sample_frequencies(pulse, n_samples=42) spectrum = 4e-3 / abs(omega) pulse.cache_control_matrix(omega, cache_intermediates=True) cumulant_function_first_order = numeric.calculate_cumulant_function( pulse, spectrum, omega, second_order=False) cumulant_function_second_order = numeric.calculate_cumulant_function( pulse, spectrum, omega, second_order=True) # Make sure first and second order are --roughly -- of same order # of magnitude. Unlike the frequency shifts themselves, the # second order contributions to the cumulant function vanish on the # diagonal, whereas the first order contributions dominate. Hence, # be quite lenient. second_order_contribution = (cumulant_function_second_order - cumulant_function_first_order) rel = (np.linalg.norm(cumulant_function_first_order) / np.linalg.norm(second_order_contribution)) # Second order terms should be anti-hermitian self.assertArrayAlmostEqual( second_order_contribution, -second_order_contribution.transpose(0, 2, 1), atol=1e-16) self.assertEqual(cumulant_function_first_order.shape, cumulant_function_second_order.shape) self.assertLessEqual(rel, 200) self.assertGreaterEqual(rel, 1 / 10)
def test_concatenate_without_filter_function(self): """Concatenate two Spin Echos without filter functions.""" tau = 10 tau_pi = 1e-4 n = 1 H_c_SE, dt_SE = testutil.generate_dd_hamiltonian(n, tau=tau, tau_pi=tau_pi, dd_type='cpmg') n_oper = util.paulis[3] H_n_SE = [[n_oper, np.ones_like(dt_SE)]] SE_1 = ff.PulseSequence(H_c_SE, H_n_SE, dt_SE) SE_2 = ff.PulseSequence(H_c_SE, H_n_SE, dt_SE) H_c_CPMG, dt_CPMG = testutil.generate_dd_hamiltonian(2*n, tau=2*tau, tau_pi=tau_pi, dd_type='cpmg') H_n_CPMG = [[n_oper, np.ones_like(dt_CPMG)]] CPMG = ff.PulseSequence(H_c_CPMG, H_n_CPMG, dt_CPMG) CPMG_concat = SE_1 @ SE_2 self.assertEqual(CPMG_concat, CPMG) # Should still be None self.assertEqual(CPMG_concat._filter_function, CPMG._filter_function) # Test if calculation of composite filter function can be enforced with # omega != None omega = util.get_sample_frequencies(SE_1) CPMG_concat = ff.concatenate((SE_1, SE_2), omega=omega) self.assertIsNotNone(CPMG_concat._filter_function) pulse = testutil.rand_pulse_sequence(2, 1, 2, 3) # Concatenate pulses without filter functions with self.assertRaises(TypeError): # Not all pulse sequence pulse_sequence.concatenate_without_filter_function([pulse, 2]) with self.assertRaises(TypeError): # Not iterable pulse_sequence.concatenate_without_filter_function(pulse) with self.assertRaises(ValueError): # Incompatible Hamiltonian shapes pulse_sequence.concatenate_without_filter_function( [testutil.rand_pulse_sequence(2, 1), testutil.rand_pulse_sequence(3, 1)] ) with self.assertRaises(ValueError): # Incompatible bases pulse = testutil.rand_pulse_sequence(4, 1, btype='GGM') cpulse = copy(pulse) cpulse.basis = ff.Basis.pauli(2) pulse_sequence.concatenate_without_filter_function([pulse, cpulse]) pulse = pulse_sequence.concatenate_without_filter_function( [pulse, pulse], return_identifier_mappings=False ) self.assertFalse(pulse.is_cached('filter function'))
def test_caching(self): """Test caching""" pulse_1 = testutil.rand_pulse_sequence(2, 10, btype='Pauli') pulse_2 = testutil.rand_pulse_sequence(2, 10, btype='Pauli') pulse_2.dt = pulse_1.dt pulse_2.t = pulse_1.t omega = util.get_sample_frequencies(pulse_1, 50) # diagonalize one pulse pulse_1.diagonalize() extended_pulse = ff.extend([(pulse_1, 0), (pulse_2, 1)]) self.assertIsNone(extended_pulse._eigvals) self.assertIsNone(extended_pulse._eigvecs) self.assertIsNone(extended_pulse._propagators) self.assertIsNone(extended_pulse._total_propagator) self.assertIsNone(extended_pulse._total_propagator_liouville) self.assertIsNone(extended_pulse._total_phases) self.assertIsNone(extended_pulse._control_matrix) self.assertIsNone(extended_pulse._filter_function) # override extended_pulse = ff.extend([(pulse_1, 0), (pulse_2, 1)], cache_diagonalization=True) self.assertIsNotNone(extended_pulse._eigvals) self.assertIsNotNone(extended_pulse._eigvecs) self.assertIsNotNone(extended_pulse._propagators) self.assertIsNotNone(extended_pulse._total_propagator) self.assertIsNone(extended_pulse._total_propagator_liouville) self.assertIsNone(extended_pulse._total_phases) self.assertIsNone(extended_pulse._control_matrix) self.assertIsNone(extended_pulse._filter_function) # diagonalize both pulse_2.diagonalize() extended_pulse = ff.extend([(pulse_1, 0), (pulse_2, 1)]) self.assertIsNotNone(extended_pulse._eigvals) self.assertIsNotNone(extended_pulse._eigvecs) self.assertIsNotNone(extended_pulse._propagators) self.assertIsNotNone(extended_pulse._total_propagator) self.assertIsNone(extended_pulse._total_propagator_liouville) self.assertIsNone(extended_pulse._total_phases) self.assertIsNone(extended_pulse._control_matrix) self.assertIsNone(extended_pulse._filter_function) # override extended_pulse = ff.extend([(pulse_1, 0), (pulse_2, 1)], cache_diagonalization=False) self.assertIsNone(extended_pulse._eigvals) self.assertIsNone(extended_pulse._eigvecs) self.assertIsNone(extended_pulse._propagators) # Total_propagators is still cached self.assertIsNotNone(extended_pulse._total_propagator) self.assertIsNone(extended_pulse._total_propagator_liouville) self.assertIsNone(extended_pulse._total_phases) self.assertIsNone(extended_pulse._control_matrix) self.assertIsNone(extended_pulse._filter_function) # Get filter function for one pulse pulse_1.cache_filter_function(omega) extended_pulse = ff.extend([(pulse_1, 0), (pulse_2, 1)]) self.assertIsNone(extended_pulse._total_propagator_liouville) self.assertIsNone(extended_pulse._total_phases) self.assertIsNone(extended_pulse._control_matrix) self.assertIsNone(extended_pulse._filter_function) # override extended_pulse = ff.extend([(pulse_1, 0), (pulse_2, 1)], cache_filter_function=True, omega=omega) self.assertIsNotNone(extended_pulse._total_propagator_liouville) self.assertIsNotNone(extended_pulse._total_phases) self.assertIsNotNone(extended_pulse._control_matrix) self.assertIsNotNone(extended_pulse._filter_function) # Get filter function for both pulse_2.cache_filter_function(omega) extended_pulse = ff.extend([(pulse_1, 0), (pulse_2, 1)]) self.assertIsNotNone(extended_pulse._total_propagator_liouville) self.assertIsNotNone(extended_pulse._total_phases) self.assertIsNotNone(extended_pulse._control_matrix) self.assertIsNotNone(extended_pulse._filter_function) # override extended_pulse = ff.extend([(pulse_1, 0), (pulse_2, 1)], cache_filter_function=False) self.assertIsNone(extended_pulse._total_propagator_liouville) self.assertIsNone(extended_pulse._total_phases) self.assertIsNone(extended_pulse._control_matrix) self.assertIsNone(extended_pulse._filter_function)