def test_control_matrix(self): """Test control matrix for traceless and non-traceless bases""" n_opers = testutil.rand_herm(3, 4) n_opers_traceless = testutil.rand_herm_traceless(3, 4) basis = ff.Basis(testutil.rand_herm(3), traceless=False) basis_traceless = ff.Basis(testutil.rand_herm_traceless(3), traceless=True) base_pulse = testutil.rand_pulse_sequence(3, 10, 4, 4) omega = np.logspace(-1, 1, 51) for i, base in enumerate((basis, basis_traceless)): for j, n_ops in enumerate((n_opers, n_opers_traceless)): pulse = copy(base_pulse) pulse.n_opers = n_ops pulse.basis = base R = pulse.get_control_matrix(omega) if i == 0 and j == 0: # base not traceless, nopers not traceless self.assertTrue((R[:, 0] != 0).all()) elif i == 0 and j == 1: # base not traceless, nopers traceless self.assertTrue((R[:, 0] != 0).all()) elif i == 1 and j == 0: # base traceless, nopers not traceless self.assertTrue((R[:, 0] != 0).all()) elif i == 1 and j == 1: # base traceless, nopers traceless self.assertTrue(np.allclose(R[:, 0], 0))
def test_basis_properties(self): """Basis orthonormal and of correct dimensions""" d = rng.randint(2, 17) n = rng.randint(1, 5) ggm_basis = ff.Basis.ggm(d) pauli_basis = ff.Basis.pauli(n) custom_basis = ff.Basis(testutil.rand_herm(d), traceless=False) btypes = ('Pauli', 'GGM', 'Custom') bases = (pauli_basis, ggm_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.randint(0, d**2)] in base) else: self.assertEqual(2**n, base.d) # Check if __contains__ works as expected self.assertTrue(base[rng.randint(0, (2**n)**2)] 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 == 'Custom': self.assertTrue(base.istraceless) else: self.assertFalse(base.istraceless) 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 = ff.util.paulis[1].view(ff.Basis) self.assertTrue(basis.isorthonorm) self.assertArrayEqual(basis.T, basis.view(np.ndarray).T)
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.randint(2, 10, (5, )): psi = rng.standard_normal((d, 1)) + 1j * rng.standard_normal( (d, 1)) # Also test broadcasting U = testutil.rand_herm(d, rng.randint(1, 11)).squeeze() phase = rng.standard_normal() result = util.oper_equiv(psi, psi * np.exp(1j * phase)) self.assertTrue(result[0]) self.assertAlmostEqual(result[1], phase, places=5) result = util.oper_equiv(psi * np.exp(1j * phase), psi) self.assertTrue(result[0]) self.assertAlmostEqual(result[1], -phase, 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(result[1], phase, 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(result[1], phase, atol=1e-5) result = util.oper_equiv(U * np.exp(1j * phase), U) self.assertTrue(np.all(result[0])) self.assertArrayAlmostEqual(result[1], -phase, 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(result[1], phase) result = util.oper_equiv(U, U + 1) self.assertFalse(np.all(result[0]))
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.randint(2, 7) # Get a random traceless hermitian operator oper = testutil.rand_herm_traceless(d) # ... and build a basis from it b = ff.Basis(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.randint(0, len(inds))) for _ in range(rng.randint(1, d**2))) elems = b[tup, ...] basis = ff.Basis(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.randint(2, 7) # Get a random hermitian operator oper = testutil.rand_herm(d) # ... and build a basis from it b = ff.Basis(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.randint(0, len(inds))) for _ in range(rng.randint(1, d**2))) elems = b[tup, ...] basis = ff.Basis(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_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.randint(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.randint(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_oper_equiv(self): with self.assertRaises(ValueError): util.oper_equiv(*[np.ones((1, 2, 3))] * 2) for d in rng.randint(2, 10, (5, )): psi = rng.standard_normal((d, 1)) + 1j * rng.standard_normal( (d, 1)) U = testutil.rand_herm(d).squeeze() phase = rng.standard_normal() result = util.oper_equiv(psi, psi * np.exp(1j * phase)) self.assertTrue(result[0]) self.assertAlmostEqual(result[1], phase, places=5) result = util.oper_equiv(psi * np.exp(1j * phase), psi) self.assertTrue(result[0]) self.assertAlmostEqual(result[1], -phase, 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.assertAlmostEqual(result[1], phase, places=5) result = util.oper_equiv(psi, psi + 1) self.assertFalse(result[0]) result = util.oper_equiv(U, U * np.exp(1j * phase)) self.assertTrue(result[0]) self.assertAlmostEqual(result[1], phase, places=5) result = util.oper_equiv(U * np.exp(1j * phase), U) self.assertTrue(result[0]) self.assertAlmostEqual(result[1], -phase, places=5) U /= np.sqrt(util.dot_HS(U, U)) result = util.oper_equiv(U, U * np.exp(1j * phase), normalized=True, eps=1e-10) self.assertTrue(result[0]) self.assertAlmostEqual(result[1], phase) result = util.oper_equiv(U, U + 1) self.assertFalse(result[0])
def test_basis_constructor(self): """Test the constructor for several failure modes""" # Constructing from given elements should check for __getitem__ with self.assertRaises(TypeError): _ = ff.Basis(1) # All elements should be either sparse, Qobj, or ndarray elems = [ util.paulis[1], COO.from_numpy(util.paulis[3]), [[0, 1], [1, 0]] ] with self.assertRaises(TypeError): _ = ff.Basis(elems) # Too many elements with self.assertRaises(ValueError): _ = ff.Basis(rng.standard_normal((5, 2, 2))) # Non traceless elems but traceless basis requested with self.assertRaises(ValueError): _ = ff.Basis.from_partial(np.ones((2, 2)), traceless=True) # Incorrect number of labels for default constructor with self.assertRaises(ValueError): _ = ff.Basis(util.paulis, labels=['a', 'b', 'c']) # Incorrect number of labels for from_partial constructor with self.assertRaises(ValueError): _ = ff.Basis.from_partial(util.paulis[:2], labels=['a', 'b', 'c']) # from_partial constructor should move identity label to the front basis = ff.Basis.from_partial([util.paulis[1], util.paulis[0]], labels=['x', 'i']) self.assertEqual(basis.labels[:2], ['i', 'x']) self.assertEqual(basis.labels[2:], ['$C_{2}$', '$C_{3}$']) # from_partial constructor should copy labels if it can partial_basis = ff.Basis.pauli(1)[[1, 3]] partial_basis.labels = [ partial_basis.labels[1], partial_basis.labels[3] ] basis = ff.Basis.from_partial(partial_basis) self.assertEqual(basis.labels[:2], partial_basis.labels) self.assertEqual(basis.labels[2:], ['$C_{2}$', '$C_{3}$']) # Default constructor should return 3d array also for single 2d element basis = ff.Basis(rng.standard_normal((2, 2))) self.assertEqual(basis.shape, (1, 2, 2)) # from_partial constructor should return same basis for 2d or 3d input elems = testutil.rand_herm(3) basis1 = ff.Basis.from_partial(elems, labels=['weif']) basis2 = ff.Basis.from_partial(elems.squeeze(), labels=['weif']) self.assertEqual(basis1, basis2) # Calling with only the identity should work with traceless true or false self.assertEqual(ff.Basis(np.eye(2), traceless=False), ff.Basis(np.eye(2), traceless=True)) # Constructing a basis from a basis should work _ = ff.Basis(ff.Basis.ggm(2)[1:]) # Constructing should not change the elements elems = rng.standard_normal((6, 3, 3)) basis = ff.Basis(elems) self.assertArrayEqual(elems, basis)
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)