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_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_basis_generation_from_partial_pauli(self): """"Generate complete basis from partial elements of a Pauli basis""" # Do test runs with random elements from a Pauli basis in (2 ... 8) # dimensions for _ in range(50): n = rng.integers(1, 4) d = 2**n b = ff.Basis.pauli(n) inds = [i for i in range(d**2)] tup = tuple( inds.pop(rng.integers(0, len(inds))) for _ in range(rng.integers(1, d**2))) elems = b[tup, ...] basis = ff.Basis.from_partial(elems) self.assertTrue(basis.isorthonorm) self.assertTrue(basis.isherm) self.assertTrue(basis.istraceless) self.assertTrue(basis.iscomplete) self.assertTrue(all(elem in basis for elem in elems)) with self.assertWarns(UserWarning): ff.Basis.from_partial([basis[0], 1j * basis[1]]) with self.assertRaises(ValueError): ff.Basis.from_partial([basis[0], basis[0] + basis[1]])
def test_tensor_transpose(self): # Test basic functionality paulis = np.array(util.paulis) I, X, Y, Z = paulis arr = util.tensor(I, X, Y, Z) arr_dims = [[2] * 4] * 2 order = np.arange(4) for _ in range(20): order = rng.permutation(order) r = util.tensor_transpose(arr, order, arr_dims) self.assertArrayAlmostEqual(r, util.tensor(*paulis[order])) # Check exceptions with self.assertRaises(ValueError): # wrong arr_dims (too few dims) r = util.tensor_transpose(arr, order, [[2] * 3] * 2) with self.assertRaises(ValueError): # wrong arr_dims (too few dims) r = util.tensor_transpose(arr, order, [[2] * 4] * 1) with self.assertRaises(ValueError): # wrong arr_dims (dims too large) r = util.tensor_transpose(arr, order, [[3] * 4] * 2) with self.assertRaises(ValueError): # wrong order (too few axes) r = util.tensor_transpose(arr, (0, 1, 2), arr_dims) with self.assertRaises(ValueError): # wrong order (index 4 too large) r = util.tensor_transpose(arr, (1, 2, 3, 4), arr_dims) with self.assertRaises(ValueError): # wrong order (not unique axes) r = util.tensor_transpose(arr, (1, 1, 1, 1), arr_dims) with self.assertRaises(TypeError): # wrong order (floats instead of ints) r = util.tensor_transpose(arr, (0., 1., 2., 3.), arr_dims) # random tests for rank, n_args, n_broadcast in zip(rng.integers(1, 4, 10), rng.integers(3, 6, 10), rng.integers(1, 11, 10)): arrs = rng.standard_normal((n_args, n_broadcast, *[2] * rank)) order = rng.permutation(n_args) arr_dims = [[2] * n_args] * rank r = util.tensor_transpose(util.tensor(*arrs, rank=rank), order=order, arr_dims=arr_dims, rank=rank) self.assertArrayAlmostEqual(r, util.tensor(*arrs[order], rank=rank))
def test_tensor(self): shapes = [(1, 2, 3, 4, 5), (5, 4, 3, 2, 1)] A = rng.standard_normal(shapes[0]) B = rng.standard_normal(shapes[1]) with self.assertRaises(ValueError): util.tensor(A, B) shapes = [(3, 2, 1), (3, 4, 2)] A = rng.standard_normal(shapes[0]) B = rng.standard_normal(shapes[1]) with self.assertRaises(ValueError): util.tensor(A, B, rank=1) self.assertEqual(util.tensor(A, B, rank=2).shape, (3, 8, 2)) self.assertEqual(util.tensor(A, B, rank=3).shape, (9, 8, 2)) shapes = [(10, 1, 3, 2), (10, 1, 2, 3)] A = rng.standard_normal(shapes[0]) B = rng.standard_normal(shapes[1]) self.assertEqual(util.tensor(A, B).shape, (10, 1, 6, 6)) shapes = [(3, 5, 4, 4), (3, 5, 4, 4)] A = rng.standard_normal(shapes[0]) B = rng.standard_normal(shapes[1]) self.assertEqual(util.tensor(A, B).shape, (3, 5, 16, 16)) d = rng.integers(2, 9) eye = np.eye(d) for i in range(d): for j in range(d): A, B = eye[i:i + 1, :], eye[:, j:j + 1] self.assertArrayEqual(util.tensor(A, B, rank=1), np.kron(A, B)) i, j = rng.integers(0, 4, (2, )) A, B = util.paulis[i], util.paulis[j] self.assertArrayEqual(util.tensor(A, B), np.kron(A, B)) args = [ rng.standard_normal((4, 1, 2)), rng.standard_normal((3, 2)), rng.standard_normal((4, 3, 5)) ] self.assertEqual(util.tensor(*args, rank=1).shape, (4, 3, 20)) self.assertEqual(util.tensor(*args, rank=2).shape, (4, 9, 20)) self.assertEqual(util.tensor(*args, rank=3).shape, (16, 9, 20)) args = [rng.standard_normal((2, 3, 4)), rng.standard_normal((4, 3))] with self.assertRaises(ValueError) as err: util.tensor(*args, rank=1) msg = 'Incompatible shapes (2, 3, 4) and (4, 3) for tensor product of rank 1.' self.assertEqual(msg, str(err.exception))
def test_liouville_is_cCP(self): for d in rng.integers(2, 9, (15, )): # (anti-) Hermitian generator, should always be cCP H = 1j * testutil.rand_herm(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) H_sup = (np.einsum('iab,...bc,jca', basis, H, basis, optimize=['einsum_path', (0, 1), (0, 1)]) - np.einsum('iab,jbc,...ca', basis, basis, H, optimize=['einsum_path', (0, 2), (0, 1)])) cCP, (D, V) = superoperator.liouville_is_cCP(H_sup, basis, True) _cCP = superoperator.liouville_is_cCP(H_sup, basis, False) self.assertArrayEqual(cCP, _cCP) self.assertTrue(np.all(cCP)) if H_sup.ndim == 2: self.assertIsInstance(cCP, (bool, np.bool8)) else: self.assertEqual(cCP.shape[0], H_sup.shape[0]) self.assertArrayAlmostEqual(D, 0, atol=1e-14) self.assertTrue( ff.util.oper_equiv(V, np.eye(d**2)[None, :, :], normalized=True)) pulse = testutil.rand_pulse_sequence(d, 1) omega = ff.util.get_sample_frequencies(pulse) S = 1 / abs(omega)**2 K_sup = ff.numeric.calculate_cumulant_function(pulse, S, omega) cCP = superoperator.liouville_is_cCP(K_sup, pulse.basis, False, atol=1e-13) self.assertTrue(np.all(cCP)) if K_sup.ndim == 2: self.assertIsInstance(cCP, (bool, np.bool8)) else: self.assertEqual(cCP.shape[0], K_sup.shape[0])
def test_dot_HS(self): U, V = rng.integers(0, 100, (2, 2, 2)) S = util.dot_HS(U, V) T = util.dot_HS(U, V, eps=0) self.assertArrayEqual(S, T) for d in rng.integers(2, 10, (5, )): U, V = testutil.rand_herm(d, 2) self.assertArrayAlmostEqual(util.dot_HS(U, V), (U.conj().T @ V).trace()) U = testutil.rand_unit(d).squeeze() self.assertEqual(util.dot_HS(U, U), d) self.assertEqual(util.dot_HS(U, U + 1e-14, eps=1e-10), 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.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_calculation_with_unitaries(self): for d, G in zip(rng.integers(2, 9, (11, )), rng.integers(2, 11, (11, ))): pulses = [ testutil.rand_pulse_sequence(d, rng.integers(1, 11), 2, 4) for g in range(G) ] for pulse in pulses: # All pulses should have same n_opers for xxx_from_atomic pulse.n_opers = pulses[0].n_opers pulse.n_oper_identifiers = pulses[0].n_oper_identifiers if rng.integers(0, 2): pulse._t = None omega = rng.random(17) B_atomic = np.array([ numeric.calculate_noise_operators_from_scratch( pulse.eigvals, pulse.eigvecs, pulse.propagators, omega, pulse.n_opers, pulse.n_coeffs, pulse.dt, pulse.t) for pulse in pulses ]) R_atomic = np.array([ numeric.calculate_control_matrix_from_scratch( pulse.eigvals, pulse.eigvecs, pulse.propagators, omega, pulse.basis, pulse.n_opers, pulse.n_coeffs, pulse.dt, pulse.t) for pulse in pulses ]) self.assertArrayAlmostEqual(ff.basis.expand(B_atomic, pulse.basis), R_atomic.transpose(0, 3, 1, 2), atol=1e-14) B = numeric.calculate_noise_operators_from_atomic( np.array([pulse.get_total_phases(omega) for pulse in pulses]), B_atomic, np.array([pulse.total_propagator for pulse in pulses])) R = numeric.calculate_control_matrix_from_atomic( np.array([pulse.get_total_phases(omega) for pulse in pulses]), R_atomic, np.array( [pulse.total_propagator_liouville for pulse in pulses])) self.assertArrayAlmostEqual(ff.basis.expand(B, pulse.basis), R.transpose(2, 0, 1), atol=1e-14)
def test_oper_equiv(self): self.assertFalse( util.oper_equiv(qutip.rand_ket(2), qutip.rand_dm(2))[0]) for d in rng.integers(2, 10, (5, )): psi = qutip.rand_ket(d) U = qutip.rand_dm(d) phase = rng.standard_normal() result = util.oper_equiv(psi, psi * np.exp(1j * phase)) self.assertTrue(result[0]) self.assertAlmostEqual(np.mod(result[1], 2 * np.pi), np.mod(phase, 2 * np.pi), places=5) result = util.oper_equiv(psi * np.exp(1j * phase), psi) self.assertTrue(result[0]) self.assertAlmostEqual(np.mod(result[1], 2 * np.pi), np.mod(-phase, 2 * np.pi), places=5) result = util.oper_equiv(U, U * np.exp(1j * phase)) self.assertTrue(result[0]) self.assertAlmostEqual(np.mod(result[1], 2 * np.pi), np.mod(phase, 2 * np.pi), places=5) result = util.oper_equiv(U * np.exp(1j * phase), U) self.assertTrue(result[0]) self.assertAlmostEqual(np.mod(result[1], 2 * np.pi), np.mod(-phase, 2 * np.pi), places=5)
def test_basis_generation_from_partial_ggm(self): """"Generate complete basis from partial elements of a GGM basis""" # Do test runs with random elements from a GGM basis in (2 ... 8) # dimensions for _ in range(50): d = rng.integers(2, 9) b = ff.Basis.ggm(d) inds = [i for i in range(d**2)] tup = tuple( inds.pop(rng.integers(0, len(inds))) for _ in range(rng.integers(1, d**2))) elems = b[tup, ...] basis = ff.Basis.from_partial(elems) self.assertTrue(basis.isorthonorm) self.assertTrue(basis.isherm) self.assertTrue(basis.istraceless) self.assertTrue(basis.iscomplete) self.assertTrue(all(elem in basis for elem in elems))
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_all_array_equal(self): for n in rng.integers(2, 10, (10, )): gen = (np.ones((10, 10)) for _ in range(n)) lst = [np.ones((10, 10)) for _ in range(n)] self.assertTrue(util.all_array_equal(gen)) self.assertTrue(util.all_array_equal(lst)) gen = (np.arange(9).reshape(3, 3) + i for i in range(n)) lst = [np.arange(9).reshape(3, 3) + i for i in range(n)] self.assertFalse(util.all_array_equal(gen)) self.assertFalse(util.all_array_equal(lst))
def test_remove_float_errors(self): for eps_scale in (None, 2): scale = eps_scale or 1 for dtype in (float, complex): arr = np.zeros((10, 10), dtype=dtype) arr += scale * np.finfo(arr.dtype).eps * rng.random(arr.shape) arr[rng.integers(0, 2, arr.shape, dtype=bool)] *= -1 arr = util.remove_float_errors(arr, eps_scale) self.assertArrayEqual(arr, np.zeros(arr.shape, dtype=dtype)) arr = np.array(0, dtype=dtype) arr += scale * np.finfo(arr.dtype).eps * rng.random() arr = util.remove_float_errors(arr, eps_scale) self.assertArrayEqual(arr, np.zeros(arr.shape, dtype=dtype))
def test_dot_HS(self): for d in rng.integers(2, 10, (5, )): U = qutip.rand_herm(d) V = qutip.rand_herm(d) self.assertArrayAlmostEqual(util.dot_HS(U, V), (U.dag() * V).tr()) U = qutip.rand_unitary(d) self.assertEqual(util.dot_HS(U, U), d) self.assertEqual(util.dot_HS(U, U + 1e-14, eps=1e-10), d) self.assertArrayAlmostEqual(util.dot_HS(U, V), (U.dag() * V).tr()) U = qutip.rand_unitary(d) self.assertEqual(util.dot_HS(U, U), d) self.assertEqual(util.dot_HS(U, U + 1e-14, eps=1e-10), d)
def test_infidelity_convergence(self): omega = { 'omega_IR': 0, 'omega_UV': 2, 'spacing': 'linear', 'n_min': 10, 'n_max': 50, 'n_points': 4 } def spectrum(omega): return omega**0 simple_pulse = testutil.rand_pulse_sequence(2, 1, 1, 1) complicated_pulse = testutil.rand_pulse_sequence(2, 100, 3, 3) with self.assertRaises(TypeError): n, infids = ff.infidelity(simple_pulse, spectrum, [], test_convergence=True) with self.assertRaises(TypeError): n, infids = ff.infidelity(simple_pulse, [1, 2, 3], dict(spacing='foobar'), test_convergence=True) with self.assertRaises(ValueError): n, infids = ff.infidelity(simple_pulse, spectrum, dict(spacing='foobar'), test_convergence=True) # Test with default args n, infids = ff.infidelity(simple_pulse, spectrum, {}, test_convergence=True) # Test with non-default args identifiers = rng.choice(complicated_pulse.n_oper_identifiers, rng.integers(1, 4)) n, infids = ff.infidelity(complicated_pulse, spectrum, omega, test_convergence=True, n_oper_identifiers=identifiers)
def test_basis_generation_from_partial_random(self): """"Generate complete basis from partial elements of a random basis""" # Do 25 test runs with random elements from a random basis in # (2 ... 8) dimensions for _ in range(25): d = rng.integers(2, 7) # Get a random traceless hermitian operator oper = testutil.rand_herm_traceless(d) # ... and build a basis from it b = ff.Basis.from_partial(oper) self.assertTrue(b.isorthonorm) self.assertTrue(b.isherm) self.assertTrue(b.istraceless) self.assertTrue(b.iscomplete) # Choose random elements from that basis and generate a new basis # from it inds = [i for i in range(d**2)] tup = tuple( inds.pop(rng.integers(0, len(inds))) for _ in range(rng.integers(1, d**2))) elems = b[tup, ...] basis = ff.Basis.from_partial(elems) self.assertTrue(basis.isorthonorm) self.assertTrue(basis.isherm) self.assertTrue(basis.istraceless) self.assertTrue(basis.iscomplete) self.assertTrue(all(elem in basis for elem in elems)) # Test runs with non-traceless opers for _ in range(25): d = rng.integers(2, 7) # Get a random hermitian operator oper = testutil.rand_herm(d) # ... and build a basis from it b = ff.Basis.from_partial(oper) self.assertTrue(b.isorthonorm) self.assertTrue(b.isherm) self.assertFalse(b.istraceless) self.assertTrue(b.iscomplete) # Choose random elements from that basis and generate a new basis # from it inds = [i for i in range(d**2)] tup = tuple( inds.pop(rng.integers(0, len(inds))) for _ in range(rng.integers(1, d**2))) elems = b[tup, ...] basis = ff.Basis.from_partial(elems) self.assertTrue(basis.isorthonorm) self.assertTrue(basis.isherm) self.assertFalse(basis.istraceless) self.assertTrue(basis.iscomplete) self.assertTrue(all(elem in basis for elem in elems))
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_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_pulse_sequence_attributes(self): """Test attributes of single instance""" X, Y, Z = util.paulis[1:] n_dt = rng.integers(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.integers(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.integers(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') A._t = None A._tau = None # 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)) # Test t, tau, and duration properties pulse = testutil.rand_pulse_sequence(2, 3, 1, 1) self.assertIs(pulse._t, None) self.assertIs(pulse._tau, None) self.assertArrayEqual(pulse.t, [0, *pulse.dt.cumsum()]) self.assertEqual(pulse.tau, pulse.t[-1]) self.assertEqual(pulse.duration, pulse.tau)
def test_pulse_sequence_constructor(self): """Test constructing a PulseSequence.""" base_pulse = testutil.rand_pulse_sequence(2, 5, 3, 3) H_c = list( zip(base_pulse.c_opers, base_pulse.c_coeffs, base_pulse.c_oper_identifiers)) H_n = list( zip(base_pulse.n_opers, base_pulse.n_coeffs, base_pulse.n_oper_identifiers)) dt = base_pulse.dt for i in range(3): H_c[i] = list(H_c[i]) H_n[i] = list(H_n[i]) with self.assertRaises(TypeError): # Not enough positional arguments ff.PulseSequence(H_c, H_n) with self.assertRaises(TypeError): # dt not a sequence ff.PulseSequence(H_c, H_n, dt[0]) idx = rng.integers(0, 5) with self.assertRaises(ValueError): # negative dt dt[idx] *= -1 ff.PulseSequence(H_c, H_n, dt) dt[idx] *= -1 with self.assertRaises(ValueError): # imaginary dt dt = dt.astype(complex) dt[idx] += 1j ff.PulseSequence(H_c, H_n, dt) dt = dt.real basis = ff.Basis.pauli(1) with self.assertRaises(ValueError): # basis not Basis instance ff.PulseSequence(H_c, H_n, dt, basis.view(np.ndarray)) with self.assertRaises(ValueError): # basis not square ff.PulseSequence(H_c, H_n, dt, basis.reshape(4, 1, 4)) with self.assertRaises(TypeError): # Control Hamiltonian not list or tuple ff.PulseSequence(np.array(H_c, dtype=object), H_n, dt) with self.assertRaises(TypeError): # Noise Hamiltonian not list or tuple ff.PulseSequence(H_c, np.array(H_n, dtype=object), dt) with self.assertRaises(TypeError): # Element of control Hamiltonian not list or tuple ff.PulseSequence([np.array(H_c[0], dtype=object)], H_n, dt) with self.assertRaises(TypeError): # Element of noise Hamiltonian not list or tuple ff.PulseSequence(H_c, [np.array(H_n[0], dtype=object)], dt) idx = rng.integers(0, 3) with self.assertRaises(TypeError): # Control Hamiltonian element not list or tuple H_c[idx] = dict(H_c[idx]) ff.PulseSequence(H_c, H_n, dt) H_c[idx] = list(H_c[idx]) with self.assertRaises(TypeError): # Noise Hamiltonian element not list or tuple H_n[idx] = dict(H_n[idx]) ff.PulseSequence(H_c, H_n, dt) H_n[idx] = list(H_n[idx]) with self.assertRaises(TypeError): # Control operators wrong type oper = H_c[idx][0].copy() H_c[idx][0] = dict(H_c[idx][0]) ff.PulseSequence(H_c, H_n, dt) H_c[idx][0] = oper with self.assertRaises(TypeError): # Noise operators wrong type oper = H_n[idx][0].copy() H_n[idx][0] = dict(H_n[idx][0]) ff.PulseSequence(H_c, H_n, dt) H_n[idx][0] = oper with self.assertRaises(TypeError): # Control coefficients wrong type coeff = H_c[idx][1].copy() H_c[idx][1] = H_c[idx][1][0] ff.PulseSequence(H_c, H_n, dt) H_c[idx][1] = coeff with self.assertRaises(TypeError): # Noise coefficients wrong type coeff = H_n[idx][1].copy() H_n[idx][1] = H_n[idx][1][0] ff.PulseSequence(H_c, H_n, dt) H_n[idx][1] = coeff with self.assertRaises(ValueError): # Control operators not 2d for hc in H_c: hc[0] = np.tile(hc[0], (rng.integers(2, 11), 1, 1)) ff.PulseSequence(H_c, H_n, dt) for hc in H_c: hc[0] = hc[0][0] with self.assertRaises(ValueError): # Noise operators not 2d for hn in H_n: hn[0] = np.tile(hn[0], (rng.integers(2, 11), 1, 1)) ff.PulseSequence(H_c, H_n, dt) for hn in H_n: hn[0] = hn[0][0] with self.assertRaises(ValueError): # Control operators not square for hc in H_c: hc[0] = np.tile(hc[0].reshape(1, 4), (2, 1)) ff.PulseSequence(H_c, H_n, dt) for hc in H_c: hc[0] = hc[0][0].reshape(2, 2) with self.assertRaises(ValueError): # Noise operators not square for hn in H_n: hn[0] = np.tile(hn[0].reshape(1, 4), (2, 1)) ff.PulseSequence(H_c, H_n, dt) for hn in H_n: hn[0] = hn[0][0].reshape(2, 2) with self.assertRaises(ValueError): # Control and noise operators not same dimension for hn in H_n: hn[0] = np.block([[hn[0], hn[0]], [hn[0], hn[0]]]) ff.PulseSequence(H_c, H_n, dt) for hn in H_n: hn[0] = hn[0][:2, :2] with self.assertRaises(ValueError): # Control identifiers not unique identifier = H_c[idx][2] H_c[idx][2] = H_c[idx - 1][2] ff.PulseSequence(H_c, H_n, dt) H_c[idx][2] = identifier with self.assertRaises(ValueError): # Noise identifiers not unique identifier = H_n[idx][2] H_n[idx][2] = H_n[idx - 1][2] ff.PulseSequence(H_c, H_n, dt) H_n[idx][2] = identifier coeffs = [] with self.assertRaises(ValueError): # Control coefficients not same length as dt for hc in H_c: coeffs.append(hc[1][-2:]) hc[1] = hc[1][:-2] ff.PulseSequence(H_c, H_n, dt) for i, c in enumerate(coeffs): H_c[i][1] = np.concatenate((H_c[i][1], c)) with self.assertRaises(ValueError): # Noise coefficients not same length as dt for hn in H_n: hn[1] = hn[1][:-2] ff.PulseSequence(H_c, H_n, dt) for i, c in enumerate(coeffs): H_n[i][1] = np.concatenate((H_n[i][1], c)) pulse = ff.PulseSequence(H_c, H_n, dt) # Hit __str__ and __repr__ methods pulse print(pulse) # Fewer identifiers than opers pulse_2 = ff.PulseSequence( [[util.paulis[1], [1], 'X'], [util.paulis[2], [1]]], [[util.paulis[1], [1]], [util.paulis[2], [1], 'Y']], [1]) self.assertArrayEqual(pulse_2.c_oper_identifiers, ('A_1', 'X')) self.assertArrayEqual(pulse_2.n_oper_identifiers, ('B_0', 'Y'))
def test_pulse_sequence_attributes_concat(self): """Test attributes of concatenated sequence.""" X, Y, Z = util.paulis[1:] n_dt_1 = rng.integers(5, 11) x_coeff_1 = rng.standard_normal(n_dt_1) z_coeff_1 = rng.standard_normal(n_dt_1) dt_1 = np.abs(rng.standard_normal(n_dt_1)) n_dt_2 = rng.integers(5, 11) y_coeff_2 = rng.standard_normal(n_dt_2) z_coeff_2 = rng.standard_normal(n_dt_2) dt_2 = np.abs(rng.standard_normal(n_dt_2)) pulse_1 = ff.PulseSequence([[X, x_coeff_1]], [[Z, z_coeff_1]], dt_1) pulse_2 = ff.PulseSequence([[Y, y_coeff_2]], [[Z, z_coeff_2]], dt_2) pulse_3 = ff.PulseSequence( [[Y, rng.standard_normal(2)], [X, rng.standard_normal(2)]], [[Z, np.abs(rng.standard_normal(2))]], [1, 1]) pulse_4 = ff.PulseSequence( [[Y, rng.standard_normal(2)], [X, rng.standard_normal(2)]], [[Z, np.ones(2)]], [1, 1]) pulse_5 = ff.PulseSequence([[Y, np.zeros(5), 'A_0']], [[Y, np.zeros(5), 'B_1']], 1 - rng.random(5)) # Concatenate with different noise opers pulses = [testutil.rand_pulse_sequence(2, 1) for _ in range(2)] pulses[0].omega = np.arange(10) pulses[1].omega = np.arange(10) newpulse = ff.concatenate(pulses, calc_filter_function=True) self.assertTrue(newpulse.is_cached('filter function')) with self.assertRaises(TypeError): _ = pulse_1 @ rng.standard_normal((2, 2)) # Concatenate pulses with same operators but different labels with self.assertRaises(ValueError): pulse_1 @ pulse_3 # Test nbytes property _ = pulse_1.nbytes pulse_12 = pulse_1 @ pulse_2 pulse_21 = pulse_2 @ pulse_1 pulse_45 = pulse_4 @ pulse_5 self.assertArrayEqual(pulse_12.dt, [*dt_1, *dt_2]) self.assertArrayEqual(pulse_21.dt, [*dt_2, *dt_1]) self.assertIs(pulse_12._t, None) self.assertIs(pulse_21._t, None) self.assertEqual(pulse_12._tau, pulse_1.tau + pulse_2.tau) self.assertEqual(pulse_21._tau, pulse_1.tau + pulse_2.tau) self.assertAlmostEqual(pulse_12.duration, pulse_1.duration + pulse_2.duration) self.assertAlmostEqual(pulse_21.duration, pulse_2.duration + pulse_1.duration) self.assertAlmostEqual(pulse_12.duration, pulse_21.duration) self.assertArrayAlmostEqual( pulse_12.t, [*pulse_1.t, *(pulse_2.t[1:] + pulse_1.tau)]) self.assertArrayAlmostEqual( pulse_21.t, [*pulse_2.t, *(pulse_1.t[1:] + pulse_2.tau)]) self.assertArrayEqual(pulse_12.c_opers, [X, Y]) self.assertArrayEqual(pulse_21.c_opers, [Y, X]) self.assertArrayEqual(pulse_12.c_oper_identifiers, ['A_0_0', 'A_0_1']) self.assertArrayEqual(pulse_21.c_oper_identifiers, ['A_0_0', 'A_0_1']) self.assertArrayEqual( pulse_12.c_coeffs, [[*x_coeff_1, *np.zeros(n_dt_2)], [*np.zeros(n_dt_1), *y_coeff_2]]) self.assertArrayEqual( pulse_21.c_coeffs, [[*y_coeff_2, *np.zeros(n_dt_1)], [*np.zeros(n_dt_2), *x_coeff_1]]) self.assertArrayEqual(pulse_12.n_opers, [Z]) self.assertArrayEqual(pulse_21.n_opers, [Z]) self.assertArrayEqual(pulse_12.n_oper_identifiers, ['B_0']) self.assertArrayEqual(pulse_21.n_oper_identifiers, ['B_0']) self.assertArrayEqual(pulse_12.n_coeffs, [[*z_coeff_1, *z_coeff_2]]) self.assertArrayEqual(pulse_21.n_coeffs, [[*z_coeff_2, *z_coeff_1]]) # Make sure zero coefficients are handled correctly self.assertFalse(np.any(np.isnan(pulse_45.c_coeffs))) self.assertFalse(np.any(np.isnan(pulse_45.n_coeffs))) self.assertArrayEqual(pulse_45.c_coeffs, [[*pulse_4.c_coeffs[0], *np.zeros(5)], [*pulse_4.c_coeffs[1], *np.zeros(5)]]) self.assertArrayEqual( pulse_45.n_coeffs, [[*pulse_4.n_coeffs[0], *[pulse_4.n_coeffs[0, 0]] * 5], [*[pulse_5.n_coeffs[0, 0]] * 2, *pulse_5.n_coeffs[0]]]) omega = np.linspace(-100, 100, 101) pulses = (pulse_1, pulse_2, pulse_12, pulse_21) for pulse in pulses: self.assertIsNone(pulse._total_phases) self.assertIsNone(pulse._total_propagator) self.assertIsNone(pulse._total_propagator_liouville) total_phases = pulse.get_total_phases(omega) total_propagator = pulse.total_propagator total_propagator_liouville = pulse.total_propagator_liouville self.assertArrayEqual(total_phases, pulse._total_phases) self.assertArrayEqual(total_propagator, pulse._total_propagator) self.assertArrayEqual(total_propagator_liouville, pulse._total_propagator_liouville) # Test custom identifiers letters = rng.choice(list(string.ascii_letters), size=(6, 5), replace=False) ids = [''.join(c) for c in letters[:3]] labels = [''.join(c) for c in letters[3:]] pulse = ff.PulseSequence( list(zip([X, Y, Z], rng.standard_normal((3, 2)), ids, labels)), list(zip([X, Y, Z], rng.standard_normal((3, 2)), ids, labels)), [1, 1]) self.assertArrayEqual(pulse.c_oper_identifiers, sorted(ids)) self.assertArrayEqual(pulse.n_oper_identifiers, sorted(ids)) pulse = testutil.rand_pulse_sequence(2, 7, 1, 2) periodic_pulse = ff.concatenate_periodic(pulse, 7) self.assertIs(periodic_pulse._t, None) self.assertEqual(periodic_pulse._tau, pulse.tau * 7) self.assertArrayAlmostEqual(periodic_pulse.t, [0, *periodic_pulse.dt.cumsum()])
def test_basis_expansion_and_normalization(self): """Correct expansion of operators and normalization of bases""" # dtype b = ff.Basis.ggm(3) r = ff.basis.expand(rng.standard_normal((3, 3)), b, hermitian=False) self.assertTrue(r.dtype == 'complex128') r = ff.basis.expand(testutil.rand_herm(3), b, hermitian=True) self.assertTrue(r.dtype == 'float64') b._isherm = False r = ff.basis.expand(testutil.rand_herm(3), b, hermitian=True) self.assertTrue(r.dtype == 'complex128') r = ff.basis.ggm_expand(testutil.rand_herm(3), hermitian=True) self.assertTrue(r.dtype == 'float64') r = ff.basis.ggm_expand(rng.standard_normal((3, 3)), hermitian=False) self.assertTrue(r.dtype == 'complex128') for _ in range(10): d = rng.integers(2, 16) ggm_basis = ff.Basis.ggm(d) basis = ff.Basis( np.einsum('i,ijk->ijk', rng.standard_normal(d**2), ggm_basis)) M = rng.standard_normal((d, d)) + 1j * rng.standard_normal((d, d)) M -= np.trace(M) / d coeffs = ff.basis.expand(M, basis, normalized=False) self.assertArrayAlmostEqual(M, np.einsum('i,ijk', coeffs, basis)) self.assertArrayAlmostEqual(ff.basis.expand(M, ggm_basis), ff.basis.ggm_expand(M), atol=1e-14) self.assertArrayAlmostEqual(ff.basis.ggm_expand(M), ff.basis.ggm_expand(M, traceless=True), atol=1e-14) n = rng.integers(1, 50) M = rng.standard_normal((n, d, d)) + 1j * rng.standard_normal( (n, d, d)) coeffs = ff.basis.expand(M, basis, normalized=False) self.assertArrayAlmostEqual( M, np.einsum('li,ijk->ljk', coeffs, basis)) self.assertArrayAlmostEqual(ff.basis.expand(M, ggm_basis), ff.basis.ggm_expand(M), atol=1e-14) # Argument to ggm_expand not square in last two dimensions with self.assertRaises(ValueError): ff.basis.ggm_expand(basis[..., 0]) self.assertTrue(ff.basis.normalize(basis).isorthonorm) # Basis method and function should give the same normalized = ff.basis.normalize(basis) basis.normalize() self.assertEqual(normalized, basis) # normalize single element elem = basis[1] normalized = ff.basis.normalize(elem) elem.normalize() self.assertEqual(normalized, elem) # Not matrix or sequence of matrices with self.assertRaises(ValueError): ff.basis.normalize(basis[0, 0]) # Test copy arr = rng.standard_normal((3, 2, 2)) basis = ff.Basis(arr) normalized = basis.normalize(copy=True) self.assertIsNot(normalized, basis) self.assertFalse(np.array_equal(normalized, basis)) basis.normalize() self.assertArrayEqual(normalized, basis)
def test_basis_properties(self): """Basis orthonormal and of correct dimensions""" d = rng.integers(2, 17) n = rng.integers(1, 5) ggm_basis = ff.Basis.ggm(d) pauli_basis = ff.Basis.pauli(n) from_partial_basis = ff.Basis.from_partial(testutil.rand_herm(d), traceless=False) custom_basis = ff.Basis(testutil.rand_herm_traceless(d)) btypes = ('Pauli', 'GGM', 'From partial', 'Custom') bases = (pauli_basis, ggm_basis, from_partial_basis, custom_basis) for btype, base in zip(btypes, bases): base.tidyup(eps_scale=0) self.assertTrue(base == base) self.assertFalse(base == ff.Basis.ggm(d + 1)) self.assertEqual(btype, base.btype) if not btype == 'Pauli': self.assertEqual(d, base.d) # Check if __contains__ works as expected self.assertTrue(base[rng.integers(0, len(base))] in base) else: self.assertEqual(2**n, base.d) # Check if __contains__ works as expected self.assertTrue(base[rng.integers(0, len(base))] in base) # Check if all elements of each basis are orthonormal and hermitian self.assertArrayEqual(base.T, base.view(np.ndarray).swapaxes(-1, -2)) self.assertTrue(base.isorthonorm) self.assertTrue(base.isherm) # Check if basis spans the whole space and all elems are traceless if not btype == 'From partial': self.assertTrue(base.istraceless) else: self.assertFalse(base.istraceless) if not btype == 'Custom': self.assertTrue(base.iscomplete) # Check sparse representation self.assertArrayEqual(base.sparse.todense(), base) # Test sparse cache self.assertArrayEqual(base.sparse.todense(), base) if base.d < 8: # Test very resource intense ref = np.einsum('iab,jbc,kcd,lda', *(base, ) * 4) self.assertArrayAlmostEqual(base.four_element_traces.todense(), ref, atol=1e-16) # Test setter base._four_element_traces = None base.four_element_traces = ref self.assertArrayEqual(base.four_element_traces, ref) base._print_checks() basis = util.paulis[1].view(ff.Basis) self.assertTrue(basis.isorthonorm) self.assertArrayEqual(basis.T, basis.view(np.ndarray).T)
def test_filter_function(self): """Test the filter function calculation and related methods""" for d, n_dt in zip(rng.integers(2, 10, (3, )), rng.integers(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_correlation_filter_function(self): """ Test calculation of pulse correlation filter function and control matrix. """ X, Y, Z = util.paulis[1:] T = 1 omega = np.linspace(-2e1, 2e1, 250) H_c, H_n, dt = dict(), dict(), dict() H_c['X'] = [[X, [np.pi / 2 / T]]] H_n['X'] = [[X, [1]], [Y, [1]], [Z, [1]]] dt['X'] = [T] H_c['Y'] = [[Y, [np.pi / 4 / T]]] H_n['Y'] = [[X, [1]], [Y, [1]], [Z, [1]]] dt['Y'] = [T] n_nops = 3 # Check if an exception is raised if we want to calculate the PC-FF but # one pulse has different frequencies with self.assertRaises(ValueError): pulses = dict() for i, key in enumerate(('X', 'Y')): pulses[key] = ff.PulseSequence(H_c[key], H_n[key], dt[key]) pulses[key].cache_filter_function(omega + i) ff.concatenate([pulses['X'], pulses['Y']], calc_pulse_correlation_FF=True) # Get filter functions at same frequencies [pulse.cache_filter_function(omega) for pulse in pulses.values()] pulse_1 = pulses['X'] @ pulses['Y'] pulse_2 = ff.concatenate([pulses['X'], pulses['Y']], calc_pulse_correlation_FF=True, which='fidelity') pulse_3 = ff.concatenate([pulses['X'], pulses['Y']], calc_pulse_correlation_FF=True, which='generalized') pulse_4 = copy.copy(pulse_3) pulse_4.cleanup('all') self.assertTrue(pulse_2.is_cached('control_matrix_pc')) self.assertTrue(pulse_2.is_cached('filter_function_pc')) self.assertTrue(pulse_3.is_cached('control_matrix_pc')) self.assertTrue(pulse_3.is_cached('filter_function_pc_gen')) # Check if the filter functions on the diagonals are real filter_function = pulse_2.get_pulse_correlation_filter_function() diag_1 = np.eye(2, dtype=bool) diag_2 = np.eye(3, dtype=bool) self.assertTrue(np.isreal(filter_function[diag_1][:, diag_2]).all()) self.assertEqual(pulse_1, pulse_2) self.assertEqual(pulse_2.get_pulse_correlation_filter_function().shape, (2, 2, n_nops, n_nops, len(omega))) self.assertArrayAlmostEqual( pulse_1.get_filter_function(omega), pulse_2.get_pulse_correlation_filter_function().sum((0, 1))) self.assertArrayAlmostEqual(pulse_1.get_filter_function(omega), pulse_2._filter_function) # Test the behavior of the pulse correlation control matrix with self.assertRaises(ValueError): # R wrong dimension numeric.calculate_pulse_correlation_filter_function( pulse_1._control_matrix) with self.assertRaises(util.CalculationError): # not calculated pulse_1.get_pulse_correlation_control_matrix() control_matrix_pc = pulse_2.get_pulse_correlation_control_matrix() self.assertArrayEqual( filter_function, numeric.calculate_pulse_correlation_filter_function( control_matrix_pc, 'fidelity')) control_matrix_pc = pulse_3.get_pulse_correlation_control_matrix() filter_function = pulse_3.get_pulse_correlation_filter_function( which='fidelity') self.assertArrayEqual( filter_function, numeric.calculate_pulse_correlation_filter_function( control_matrix_pc, 'fidelity')) filter_function = pulse_3.get_pulse_correlation_filter_function( which='generalized') self.assertArrayEqual( filter_function, numeric.calculate_pulse_correlation_filter_function( control_matrix_pc, 'generalized')) # Test caching pulse_4.cache_filter_function(omega, control_matrix=control_matrix_pc, which='fidelity') self.assertTrue(pulse_4.is_cached('pulse correlation control matrix')) self.assertTrue(pulse_4.is_cached('pulse correlation filter function')) self.assertTrue(pulse_4.is_cached('filter function')) self.assertArrayAlmostEqual( pulse_3.get_pulse_correlation_control_matrix(), pulse_4.get_pulse_correlation_control_matrix()) self.assertArrayAlmostEqual( pulse_3.get_pulse_correlation_filter_function(), pulse_4.get_pulse_correlation_filter_function()) self.assertArrayAlmostEqual(pulse_3.get_filter_function(omega), pulse_4.get_filter_function(omega)) pulse_4.cleanup('all') pulse_4.cache_filter_function(omega, control_matrix=control_matrix_pc, which='generalized') self.assertTrue(pulse_4.is_cached('pulse correlation control matrix')) self.assertTrue( pulse_4.is_cached('generalized pulse correlation filter function')) self.assertTrue(pulse_4.is_cached('generalized filter function')) self.assertTrue(pulse_4.is_cached('pulse correlation filter function')) self.assertTrue(pulse_4.is_cached('filter function')) self.assertArrayAlmostEqual( pulse_3.get_pulse_correlation_control_matrix(), pulse_4.get_pulse_correlation_control_matrix()) self.assertArrayAlmostEqual( pulse_3.get_pulse_correlation_filter_function('fidelity'), pulse_4.get_pulse_correlation_filter_function('fidelity')) self.assertArrayAlmostEqual( pulse_3.get_pulse_correlation_filter_function('generalized'), pulse_4.get_pulse_correlation_filter_function('generalized')) self.assertArrayAlmostEqual( pulse_3.get_filter_function(omega, which='fidelity'), pulse_4.get_filter_function(omega, which='fidelity')) self.assertArrayAlmostEqual( pulse_3.get_filter_function(omega, which='generalized'), pulse_4.get_filter_function(omega, which='generalized')) # If for some reason filter_function_pc_xy is removed, check if # recovered from control_matrix_pc pulse_2._filter_function_pc = None pulse_3._filter_function_pc_gen = None control_matrix_pc = pulse_3.get_pulse_correlation_control_matrix() filter_function = pulse_3.get_pulse_correlation_filter_function( which='fidelity') self.assertArrayEqual( filter_function, numeric.calculate_pulse_correlation_filter_function( control_matrix_pc, 'fidelity')) filter_function = pulse_3.get_pulse_correlation_filter_function( which='generalized') self.assertArrayEqual( filter_function, numeric.calculate_pulse_correlation_filter_function( control_matrix_pc, 'generalized')) spectrum = omega**0 * 1e-2 with self.assertRaises(util.CalculationError): infid_1 = ff.infidelity(pulse_1, spectrum, omega, which='correlations') with self.assertRaises(ValueError): infid_1 = ff.infidelity(pulse_1, spectrum, omega, which='foobar') for _ in range(10): n_nops = rng.integers(1, 4) identifiers = sample(['B_0', 'B_1', 'B_2'], n_nops) infid_X = ff.infidelity(pulses['X'], spectrum, omega, which='total', n_oper_identifiers=identifiers) infid_Y = ff.infidelity(pulses['Y'], spectrum, omega, which='total', n_oper_identifiers=identifiers) infid_1 = ff.infidelity(pulse_1, spectrum, omega, which='total', n_oper_identifiers=identifiers) infid_2 = ff.infidelity(pulse_2, spectrum, omega, which='correlations', n_oper_identifiers=identifiers) self.assertAlmostEqual(infid_1.sum(), infid_2.sum()) self.assertArrayAlmostEqual(infid_X, infid_2[0, 0]) self.assertArrayAlmostEqual(infid_Y, infid_2[1, 1]) # Test function for correlated noise spectra spectrum = np.array([[1e-4 / omega**2, 1e-4 * np.exp(-omega**2)], [1e-4 * np.exp(-omega**2), 1e-4 / omega**2]]) infid_1 = ff.infidelity(pulse_1, spectrum, omega, which='total', n_oper_identifiers=['B_0', 'B_2']) infid_2 = ff.infidelity(pulse_2, spectrum, omega, which='correlations', n_oper_identifiers=['B_0', 'B_2']) self.assertAlmostEqual(infid_1.sum(), infid_2.sum()) self.assertArrayAlmostEqual(infid_1, infid_2.sum(axis=(0, 1)))
def test_tensor_insert(self): I, X, Y, Z = util.paulis arr = util.tensor(X, I) with self.assertRaises(ValueError): # Test exception for empty args util.tensor_insert(arr, np.array([[]]), pos=0, arr_dims=[]) r = util.tensor_insert(arr, Y, Z, arr_dims=[[2, 2], [2, 2]], pos=0) self.assertArrayAlmostEqual(r, util.tensor(Y, Z, X, I)) r = util.tensor_insert(arr, Y, Z, arr_dims=[[2, 2], [2, 2]], pos=1) self.assertArrayAlmostEqual(r, util.tensor(X, Y, Z, I)) r = util.tensor_insert(arr, Y, Z, arr_dims=[[2, 2], [2, 2]], pos=2) self.assertArrayAlmostEqual(r, util.tensor(X, I, Y, Z)) # Test pos being negative r = util.tensor_insert(arr, Y, Z, arr_dims=[[2, 2], [2, 2]], pos=-1) self.assertArrayAlmostEqual(r, util.tensor(X, Y, Z, I)) # Test pos exception with self.assertRaises(IndexError) as err: util.tensor_insert(arr, Y, Z, arr_dims=[[2, 2], [2, 2]], pos=3) msg = 'Invalid position 3 specified. Must be between -2 and 2.' self.assertEqual(msg, str(err.exception)) # Test broadcasting and rank != 2 A, B, C = (rng.standard_normal( (2, 3, 1, 2)), rng.standard_normal( (2, 3, 1, 2)), rng.standard_normal((3, 1, 3))) arr = util.tensor(A, C, rank=1) r = util.tensor_insert(arr, B, pos=1, rank=1, arr_dims=[[2, 3]]) self.assertArrayAlmostEqual(r, util.tensor(A, B, C, rank=1)) # Test exceptions for wrong arr_dims format with self.assertRaises(ValueError): util.tensor_insert(arr, B, pos=1, rank=1, arr_dims=[[3, 3], [1, 2], [2, 1]]) with self.assertRaises(ValueError): util.tensor_insert(arr, B, pos=1, rank=1, arr_dims=[[2], [2, 1]]) A, B, C = (rng.standard_normal( (2, 3, 1, 2)), rng.standard_normal( (2, 3, 2, 2)), rng.standard_normal((3, 2, 1))) arr = util.tensor(A, C, rank=3) r = util.tensor_insert(arr, B, pos=1, rank=3, arr_dims=[[3, 3], [1, 2], [2, 1]]) self.assertArrayAlmostEqual(r, util.tensor(A, B, C, rank=3)) # Test exceptions for wrong arr_dims format with self.assertRaises(ValueError): util.tensor_insert(arr, B, pos=1, rank=3, arr_dims=[[1, 2], [2, 1]]) with self.assertRaises(ValueError): util.tensor_insert(arr, B, pos=1, rank=2, arr_dims=[[3, 3, 1], [1, 2], [2]]) A, B, C = (rng.standard_normal((2, 1)), rng.standard_normal( (1, 2, 3)), rng.standard_normal(1)) arr = util.tensor(A, C, rank=1) r = util.tensor_insert(arr, B, pos=0, rank=1, arr_dims=[[1, 1]]) self.assertArrayAlmostEqual(r, util.tensor(A, B, C, rank=1)) arrs, args = [ rng.standard_normal((2, 2, 2)), rng.standard_normal((2, 2, 2)) ] arr_dims = [[2, 2], [2, 2]] r = util.tensor_insert(util.tensor(*arrs), *args, pos=(0, 1), arr_dims=arr_dims) self.assertArrayAlmostEqual( r, util.tensor(args[0], arrs[0], args[1], arrs[1])) r = util.tensor_insert(util.tensor(*arrs), *args, pos=(0, 0), arr_dims=arr_dims) self.assertArrayAlmostEqual(r, util.tensor(*args, *arrs)) r = util.tensor_insert(util.tensor(*arrs), *args, pos=(1, 2), arr_dims=arr_dims) self.assertArrayAlmostEqual( r, util.tensor(*np.insert(arrs, (1, 2), args, axis=0))) # Test exception for wrong pos argument with self.assertRaises(ValueError): util.tensor_insert(util.tensor(*arrs), *args, pos=(0, 1, 2), arr_dims=arr_dims) # Test exception for wrong shapes arrs, args = (rng.standard_normal( (2, 4, 3, 2)), rng.standard_normal((2, 2, 3, 4))) with self.assertRaises(ValueError) as err: util.tensor_insert(util.tensor(*arrs), *args, pos=(1, 2), arr_dims=[[3, 3], [2, 2]]) err_msg = ('Could not insert arg 0 with shape (4, 9, 4) into the ' + 'array with shape (2, 3, 4) at position 1.') cause_msg = ('Incompatible shapes (2, 3, 4) and (4, 9, 4) for ' + 'tensor product of rank 2.') self.assertEqual(err_msg, str(err.exception)) self.assertEqual(cause_msg, str(err.exception.__cause__)) # Do some random tests for rank, n_args, n_broadcast in zip(rng.integers(1, 4, 10), rng.integers(3, 6, 10), rng.integers(1, 11, 10)): arrs = rng.standard_normal((n_args, n_broadcast, *[2] * rank)) split_idx = rng.integers(1, n_args - 1) ins_idx = rng.integers(split_idx - n_args, n_args - split_idx) ins_arrs = arrs[:split_idx] arr = util.tensor(*arrs[split_idx:], rank=rank) sorted_arrs = np.insert(arrs[split_idx:], ins_idx, ins_arrs, axis=0) arr_dims = [[2] * (n_args - split_idx)] * rank r = util.tensor_insert(arr, *ins_arrs, pos=ins_idx, rank=rank, arr_dims=arr_dims) self.assertArrayAlmostEqual(r, util.tensor(*sorted_arrs, rank=rank)) pos = rng.integers(-split_idx + 1, split_idx, split_idx) r = util.tensor_insert(arr, *ins_arrs, pos=pos, rank=rank, arr_dims=arr_dims) sorted_arrs = np.insert(arrs[split_idx:], pos, ins_arrs, axis=0) self.assertArrayAlmostEqual(r, util.tensor(*sorted_arrs, rank=rank), atol=1e-10)
def test_tensor_merge(self): # Test basic functionality I, X, Y, Z = util.paulis arr = util.tensor(X, Y, Z) ins = util.tensor(I, I) r1 = util.tensor_merge(arr, ins, pos=[1, 2], arr_dims=[[2] * 3, [2] * 3], ins_dims=[[2] * 2, [2] * 2]) r2 = util.tensor_merge(ins, arr, pos=[0, 1, 2], arr_dims=[[2] * 2, [2] * 2], ins_dims=[[2] * 3, [2] * 3]) self.assertArrayAlmostEqual(r1, util.tensor(X, I, Y, I, Z)) self.assertArrayAlmostEqual(r1, r2) # Test if tensor_merge and tensor_insert produce same results arr = util.tensor(Y, Z) ins = util.tensor(I, X) r1 = util.tensor_merge(arr, ins, pos=[0, 0], arr_dims=[[2] * 2, [2] * 2], ins_dims=[[2] * 2, [2] * 2]) r2 = util.tensor_insert(arr, I, X, pos=[0, 0], arr_dims=[[2] * 2, [2] * 2]) self.assertArrayAlmostEqual(r1, r2) # Test pos being negative r = util.tensor_merge(arr, ins, arr_dims=[[2, 2], [2, 2]], ins_dims=[[2, 2], [2, 2]], pos=(-1, -2)) self.assertArrayAlmostEqual(r, util.tensor(X, Y, I, Z)) # Test exceptions # Wrong dims format with self.assertRaises(ValueError): util.tensor_merge(arr, ins, pos=(1, 2), arr_dims=[[2, 2], [2, 2], [2, 2]], ins_dims=[[2, 2], [2, 2]]) with self.assertRaises(ValueError): util.tensor_merge(arr, ins, pos=(1, 2), arr_dims=[[2, 2], [2, 2]], ins_dims=[[2, 2], [2, 2], [2, 2]]) with self.assertRaises(ValueError): util.tensor_merge(arr, ins, pos=(1, 2), arr_dims=[[2, 2], [2, 2, 2]], ins_dims=[[2, 2], [2, 2]]) with self.assertRaises(ValueError): util.tensor_merge(arr, ins, pos=(1, 2), arr_dims=[[2, 2], [2, 2]], ins_dims=[[2, 2], [2, 2, 2]]) # Wrong pos with self.assertRaises(IndexError): util.tensor_merge(arr, ins, pos=(1, 3), arr_dims=[[2, 2], [2, 2]], ins_dims=[[2, 2], [2, 2]]) # Wrong dimensions given with self.assertRaises(ValueError): util.tensor_merge(arr, ins, pos=(1, 2), arr_dims=[[2, 3], [2, 2]], ins_dims=[[2, 2], [2, 2]]) with self.assertRaises(ValueError): util.tensor_merge(arr, ins, pos=(1, 2), arr_dims=[[2, 2], [2, 2]], ins_dims=[[2, 3], [2, 2]]) # Incompatible shapes arrs, args = (rng.standard_normal( (2, 4, 3, 2)), rng.standard_normal((2, 2, 3, 4))) with self.assertRaises(ValueError) as err: util.tensor_merge(util.tensor(*arrs), util.tensor(*args), pos=(1, 2), arr_dims=[[3, 3], [2, 2]], ins_dims=[[3, 3], [4, 4]]) msg = ('Incompatible shapes (2, 9, 16) and (4, 9, 4) for tensor ' + 'product of rank 2.') self.assertEqual(msg, str(err.exception)) # Test rank 1 and broadcasting arr = rng.standard_normal((2, 10, 3, 4)) ins = rng.standard_normal((2, 10, 3, 2)) r = util.tensor_merge(util.tensor(*arr, rank=1), util.tensor(*ins, rank=1), pos=[0, 1], arr_dims=[[4, 4]], ins_dims=[[2, 2]], rank=1) self.assertArrayAlmostEqual( r, util.tensor(ins[0], arr[0], ins[1], arr[1], rank=1)) # Do some random tests for rank, n_args, n_broadcast in zip(rng.integers(1, 4, 10), rng.integers(3, 6, 10), rng.integers(1, 11, 10)): arrs = rng.standard_normal((n_args, n_broadcast, *[2] * rank)) split_idx = rng.integers(1, n_args - 1) arr = util.tensor(*arrs[split_idx:], rank=rank) ins = util.tensor(*arrs[:split_idx], rank=rank) pos = rng.integers(0, split_idx, split_idx) sorted_arrs = np.insert(arrs[split_idx:], pos, arrs[:split_idx], axis=0) arr_dims = [[2] * (n_args - split_idx)] * rank ins_dims = [[2] * split_idx] * rank r = util.tensor_merge(arr, ins, pos=pos, rank=rank, arr_dims=arr_dims, ins_dims=ins_dims) self.assertArrayAlmostEqual(r, util.tensor(*sorted_arrs, rank=rank))
def test_oper_equiv(self): with self.assertRaises(ValueError): util.oper_equiv(rng.standard_normal((2, 2)), rng.standard_normal((3, 3))) for d in rng.integers(2, 10, (5, )): psi = rng.standard_normal((d, 1)) + 1j * rng.standard_normal( (d, 1)) # Also test broadcasting U = testutil.rand_herm(d, rng.integers(1, 11)).squeeze() phase = rng.standard_normal() result = util.oper_equiv(psi, psi * np.exp(1j * phase)) self.assertTrue(result[0]) self.assertAlmostEqual(np.mod(result[1], 2 * np.pi), np.mod(phase, 2 * np.pi), places=5) result = util.oper_equiv(psi * np.exp(1j * phase), psi) self.assertTrue(result[0]) self.assertAlmostEqual(np.mod(result[1], 2 * np.pi), np.mod(-phase, 2 * np.pi), places=5) psi /= np.linalg.norm(psi, ord=2) result = util.oper_equiv(psi, psi * np.exp(1j * phase), normalized=True, eps=1e-13) self.assertTrue(result[0]) self.assertArrayAlmostEqual(np.mod(result[1], 2 * np.pi), np.mod(phase, 2 * np.pi), atol=1e-5) result = util.oper_equiv(psi, psi + 1) self.assertFalse(result[0]) result = util.oper_equiv(U, U * np.exp(1j * phase)) self.assertTrue(np.all(result[0])) self.assertArrayAlmostEqual(np.mod(result[1], 2 * np.pi), np.mod(phase, 2 * np.pi), atol=1e-5) result = util.oper_equiv(U * np.exp(1j * phase), U) self.assertTrue(np.all(result[0])) self.assertArrayAlmostEqual(np.mod(result[1], 2 * np.pi), np.mod(-phase, 2 * np.pi), atol=1e-5) norm = np.sqrt(util.dot_HS(U, U)) norm = norm[:, None, None] if U.ndim == 3 else norm U /= norm # TIP: In numpy 1.18 we could just do: # U /= np.expand_dims(np.sqrt(util.dot_HS(U, U)), axis=(-1, -2)) result = util.oper_equiv(U, U * np.exp(1j * phase), normalized=True, eps=1e-10) self.assertTrue(np.all(result[0])) self.assertArrayAlmostEqual(np.mod(result[1], 2 * np.pi), np.mod(phase, 2 * np.pi)) result = util.oper_equiv(U, U + 1) self.assertFalse(np.all(result[0]))