def check_trace_preservation(supop_builder, supop_setup): """Make sure that the identity component of the density operator doesn't evolve. :param supop_builder: Function that constructs a superoperator matrix given the output from supop_setup. :param supop_setup: Function that takes a coupling op and basis (minus identity) as arguments and returns the arguments needed for supop_builder to construct the appropriate matrix. """ for dim in range(2, 3 + 1): for row in range(dim): for col in range(dim): # Couple using all the different Gell-Mann matrices c_op = gm.gellmann(row + 1, col + 1, dim) kwargs = supop_setup(c_op, basis(dim)[:-1]) D_matrix = supop_builder(**kwargs) for entry in D_matrix[-1]: assert_almost_equal(0, entry, 7) for dim in range(2, 3 + 1): np.random.seed(dim) # Couple using random matrices rand_c_op = np.random.randn(dim, dim) + 1.j * np.random.randn(dim, dim) kwargs = supop_setup(rand_c_op, basis(dim)[:-1]) D_matrix = sb.diffusion_op(**kwargs) for entry in D_matrix[-1]: assert_almost_equal(0, entry, 7)
def check_trace_preservation(supop_builder, supop_setup): """Make sure that the identity component of the density operator doesn't evolve. :param supop_builder: Function that constructs a superoperator matrix given the output from supop_setup. :param supop_setup: Function that takes a coupling op and basis (minus identity) as arguments and returns the arguments needed for supop_builder to construct the appropriate matrix. """ for dim in range(2, 3 + 1): for row in range(dim): for col in range(dim): # Couple using all the different Gell-Mann matrices c_op = gm.gellmann(row + 1, col + 1, dim) kwargs = supop_setup(c_op, basis(dim)[:-1]) D_matrix = supop_builder(**kwargs) for entry in D_matrix[-1]: assert_almost_equal(0, entry, 7) for dim in range(2, 3 + 1): np.random.seed(dim) # Couple using random matrices rand_c_op = np.random.randn(dim, dim) + 1.j*np.random.randn(dim, dim) kwargs = supop_setup(rand_c_op, basis(dim)[:-1]) D_matrix = sb.diffusion_op(**kwargs) for entry in D_matrix[-1]: assert_almost_equal(0, entry, 7)
def basis(n): """Return an orthogonal basis for n-by-n operators, with the identity in the last position. """ return [gm.gellmann(j, k, n) for j in range(1, n + 1) for k in range(1, n + 1)]
def test_gellmann(): for d in range(1, 5 + 1): matrices = [[gm.gellmann(j, k, d) for k in range(1, d + 1)] for j in range(1, d + 1)] for j in range(1, d + 1): for k in range(1, d + 1): check_hermitian(matrices[j - 1][k - 1]) if j != d or k != d: check_traceless(matrices[j - 1][k - 1]) check_norm(matrices[j - 1][k - 1], np.sqrt(2)) else: check_norm(matrices[j - 1][k - 1], np.sqrt(d)) for jj in range(1, d + 1): for kk in range(1, d + 1): if jj != j or kk != k: check_orthogonal(matrices[j - 1][k - 1], matrices[jj - 1][kk - 1])
def test_gellmann(): for d in range(1, 5 + 1): matrices = [ [ gm.gellmann(j, k, d) for k in range(1, d + 1) ] for j in range(1, d + 1) ] for j in range(1, d + 1): for k in range(1, d + 1): check_hermitian(matrices[j - 1][k - 1]) if j != d or k != d: check_traceless(matrices[j - 1][k - 1]) check_norm(matrices[j - 1][k - 1], np.sqrt(2)) else: check_norm(matrices[j - 1][k - 1], np.sqrt(d)) for jj in range(1, d + 1): for kk in range(1, d + 1): if jj != j or kk != k: check_orthogonal(matrices[j - 1][k - 1], matrices[jj - 1][kk - 1])
def orthonormalize(A): """Return an orthonormal basis in which `A` has a sparse representation. `A` will have support only on the first three elements. The first element is guaranteed to be proportional to the identity. This basis is constructed using Gram-Schmidt orthogonalization. Parameters ---------- A : numpy.array The operator to be represented sparsely. Returns ------- list of numpy.array A basis in which `A` is represented sparsely. """ d = max(A.shape) # Code won't currently work unless A is square. G = [ [ gm.gellmann(j, k, d) for k in range(1, d + 1) ] for j in range(1, d + 1) ] ordering = list(product(range(1, d + 1), range(1, d + 1))) hermitian = (A + A.conj().T)/2 antiherm = (A - A.conj().T)/2.j hermitian_comps = [np.trace(np.dot(hermitian, G[j - 1][k - 1]))/np.sqrt(2) if j != d or k!= d else np.trace(np.dot(hermitian, G[j - 1][k - 1]))/np.sqrt(d) for j, k in ordering] antiherm_comps = [np.trace(np.dot(antiherm, G[j - 1][k - 1]))/np.sqrt(2) if j != d or k!= d else np.trace(np.dot(antiherm, G[j - 1][k - 1]))/np.sqrt(d) for j, k in ordering] # Identify the Gell-Mann matrices that have the most support on the # Hermitian and anti-Hermitian parts of A (other than identity) so that they # can be discarded prior to Gram-Schmidt orthogonalization. max_comps = [max(abs(herm_comp), abs(anti_comp)) if j != d or k != d else 0 for herm_comp, anti_comp, (j, k) in zip(hermitian_comps, antiherm_comps, ordering)] # Ensure the identity is the first element in this list. ordered_max_comps = [list(enumerate(max_comps))[-1]] + \ sorted(list(enumerate(max_comps))[0:-1], key=lambda elem: elem[1]) discarded_indices = [ordered_max_comps[-1][0], ordered_max_comps[-2][0]] other_vectors = [ [ 0 if n != idx else 1 for n in range(d**2) ] for idx in range(d**2) if not idx in discarded_indices ] vector_set = np.array([other_vectors[-1], hermitian_comps, antiherm_comps] + other_vectors[0:-1]).T.real basis, R = np.linalg.qr(vector_set) basis = np.dot(basis, np.diag([1 if elem >= 0 else -1 for elem in np.diag(R)])) basis = basis.T G_new = [sum([G[j-1][k-1]*coeff/np.sqrt(2) if j != d or k!= d else G[j-1][k-1]*coeff/np.sqrt(d) for coeff, (j, k) in zip(vect, ordering)]) for vect in basis] A_coeffs = [ np.trace(np.dot(G_new[0], A)), np.trace(np.dot(G_new[1], A)), np.trace(np.dot(G_new[2], A)), ] A_recon = A_coeffs[0]*G_new[0] + A_coeffs[1]*G_new[1] + A_coeffs[2]*G_new[2] return G_new
def orthonormalize(A): """Return an orthonormal basis in which `A` has a sparse representation. `A` will have support only on the first three elements. The first element is guaranteed to be proportional to the identity. This basis is constructed using Gram-Schmidt orthogonalization. Parameters ---------- A : numpy.array The operator to be represented sparsely. Returns ------- list of numpy.array A basis in which `A` is represented sparsely. """ d = max(A.shape) # Code won't currently work unless A is square. G = [[gm.gellmann(j, k, d) for k in range(1, d + 1)] for j in range(1, d + 1)] ordering = list(product(range(1, d + 1), range(1, d + 1))) hermitian = (A + A.conj().T) / 2 antiherm = (A - A.conj().T) / 2.j hermitian_comps = [ np.trace(np.dot(hermitian, G[j - 1][k - 1])) / np.sqrt(2) if j != d or k != d else np.trace(np.dot(hermitian, G[j - 1][k - 1])) / np.sqrt(d) for j, k in ordering ] antiherm_comps = [ np.trace(np.dot(antiherm, G[j - 1][k - 1])) / np.sqrt(2) if j != d or k != d else np.trace(np.dot(antiherm, G[j - 1][k - 1])) / np.sqrt(d) for j, k in ordering ] # Identify the Gell-Mann matrices that have the most support on the # Hermitian and anti-Hermitian parts of A (other than identity) so that they # can be discarded prior to Gram-Schmidt orthogonalization. max_comps = [ max(abs(herm_comp), abs(anti_comp)) if j != d or k != d else 0 for herm_comp, anti_comp, ( j, k) in zip(hermitian_comps, antiherm_comps, ordering) ] # Ensure the identity is the first element in this list. ordered_max_comps = [list(enumerate(max_comps))[-1]] + \ sorted(list(enumerate(max_comps))[0:-1], key=lambda elem: elem[1]) discarded_indices = [ordered_max_comps[-1][0], ordered_max_comps[-2][0]] other_vectors = [[0 if n != idx else 1 for n in range(d**2)] for idx in range(d**2) if not idx in discarded_indices] vector_set = np.array( [other_vectors[-1], hermitian_comps, antiherm_comps] + other_vectors[0:-1]).T.real basis, R = np.linalg.qr(vector_set) basis = np.dot(basis, np.diag([1 if elem >= 0 else -1 for elem in np.diag(R)])) basis = basis.T G_new = [ sum([ G[j - 1][k - 1] * coeff / np.sqrt(2) if j != d or k != d else G[j - 1][k - 1] * coeff / np.sqrt(d) for coeff, (j, k) in zip(vect, ordering) ]) for vect in basis ] A_coeffs = [ np.trace(np.dot(G_new[0], A)), np.trace(np.dot(G_new[1], A)), np.trace(np.dot(G_new[2], A)), ] A_recon = A_coeffs[0] * G_new[0] + A_coeffs[1] * G_new[1] + A_coeffs[ 2] * G_new[2] return G_new