def test_expand_contract(self): # matrix that operates on 2x2 density matrices, but only on the 0-th and 3-rd # elements which correspond to the diagonals of the 2x2 density matrix. mxInStdBasis = np.array([[1,0,0,2], [0,0,0,0], [0,0,0,0], [3,0,0,4]],'d') # Reduce to a matrix operating on a density matrix space with 2 1x1 blocks (hence [1,1]) begin = Basis('std', [1,1]) end = Basis('std', 2) mxInReducedBasis = bt.resize_std_mx(mxInStdBasis, 'contract', end, begin) #mxInReducedBasis = bt.change_basis(mxInStdBasis, begin, end) notReallyContracted = bt.change_basis(mxInStdBasis, 'std', 'std', 4) correctAnswer = np.array([[ 1.0, 2.0], [ 3.0, 4.0]]) #self.assertArraysAlmostEqual( mxInReducedBasis, correctAnswer ) self.assertArraysAlmostEqual( notReallyContracted, mxInStdBasis ) expandedMx = bt.resize_std_mx(mxInReducedBasis, 'expand', begin, end) #expandedMx = bt.change_basis(mxInReducedBasis, end, begin) expandedMxAgain = bt.change_basis(expandedMx, 'std', 'std', 4) self.assertArraysAlmostEqual( expandedMx, mxInStdBasis ) self.assertArraysAlmostEqual( expandedMxAgain, mxInStdBasis )
def test_auto_expand(self): comp = Basis(matrices=[('std', 2,), ('std', 1)]) std = Basis('std', 3) mxStd = np.identity(5) test = bt.resize_std_mx(mxStd, 'expand', comp, std) test2 = bt.resize_std_mx(test, 'contract', std, comp) self.assertArraysAlmostEqual(test2, mxStd)
def test_general(self): std = Basis.cast('std', 4) std4 = Basis.cast('std', 16) std2x2 = Basis.cast([('std', 4), ('std', 4)]) gm = Basis.cast('gm', 4) from_basis, to_basis = bt.build_basis_pair(np.identity(4, 'd'), "std", "gm") from_basis, to_basis = bt.build_basis_pair(np.identity(4, 'd'), std, "gm") from_basis, to_basis = bt.build_basis_pair(np.identity(4, 'd'), "std", gm) mx = np.array([[1, 0, 0, 1], [0, 1, 2, 0], [0, 2, 1, 0], [1, 0, 0, 1]]) bt.change_basis(mx, 'std', 'gm') # shortname lookup bt.change_basis(mx, std, gm) # object bt.change_basis(mx, std, 'gm') # combination bt.flexible_change_basis(mx, std, gm) # same dimension I2x2 = np.identity(8, 'd') I4 = bt.flexible_change_basis(I2x2, std2x2, std4) self.assertArraysAlmostEqual( bt.flexible_change_basis(I4, std4, std2x2), I2x2) with self.assertRaises(AssertionError): bt.change_basis(mx, std, std4) # basis size mismatch mxInStdBasis = np.array( [[1, 0, 0, 2], [0, 0, 0, 0], [0, 0, 0, 0], [3, 0, 0, 4]], 'd') begin = Basis.cast('std', [1, 1]) end = Basis.cast('std', 4) mxInReducedBasis = bt.resize_std_mx(mxInStdBasis, 'contract', end, begin) original = bt.resize_std_mx(mxInReducedBasis, 'expand', begin, end)
def test_auto_expand(self): comp = Basis.cast([( 'std', 4, ), ('std', 1)]) std = Basis.cast('std', 9) mxStd = np.identity(5) test = bt.resize_std_mx(mxStd, 'expand', comp, std) # TODO assert intermediate correctness test2 = bt.resize_std_mx(test, 'contract', std, comp) self.assertArraysAlmostEqual(test2, mxStd)
def test_auto_expand(self): comp = Basis.cast([( 'std', 4, ), ('std', 1)]) std = Basis.cast('std', 9) mxStd = np.identity(5) test = bt.resize_std_mx(mxStd, 'expand', comp, std) # Intermediate test mxInter = np.identity(9) mxInter[2, 2] = mxInter[5, 5] = mxInter[6, 6] = mxInter[7, 7] = 0 self.assertArraysAlmostEqual(test, mxInter) test2 = bt.resize_std_mx(test, 'contract', std, comp) self.assertArraysAlmostEqual(test2, mxStd)
def jamiolkowski_iso_inv(choi_mx, choi_mx_basis='pp', op_mx_basis='pp'): """ Given a choi matrix, return the corresponding operation matrix. This function performs the inverse of :function:`jamiolkowski_iso`. Parameters ---------- choi_mx : numpy array the Choi matrix, normalized to have trace == 1, to compute operation matrix for. choi_mx_basis : Basis object The source and destination basis, respectively. Allowed values are Matrix-unit (std), Gell-Mann (gm), Pauli-product (pp), and Qutrit (qt) (or a custom basis object). op_mx_basis : Basis object The source and destination basis, respectively. Allowed values are Matrix-unit (std), Gell-Mann (gm), Pauli-product (pp), and Qutrit (qt) (or a custom basis object). Returns ------- numpy array operation matrix in the desired basis. """ choi_mx = _np.asarray(choi_mx) # will have "expanded" dimension even if bases are for reduced... N = choi_mx.shape[0] # dimension of full-basis (expanded) operation matrix if not isinstance(choi_mx_basis, _Basis): # if we're not given a basis, build choi_mx_basis = _Basis.cast(choi_mx_basis, N) # one with the full dimension dmDim = int(round(_np.sqrt(N))) # density matrix dimension #get full list of basis matrices (in std basis) BVec = _bt.basis_matrices(choi_mx_basis.create_simple_equivalent(), N) assert(len(BVec) == N) # make sure the number of basis matrices matches the dim of the choi matrix given # Invert normalization choiMx_unnorm = choi_mx * dmDim opMxInStdBasis = _np.zeros((N, N), 'complex') # in matrix unit basis of entire density matrix for i in range(N): for j in range(N): BiBj = _np.kron(BVec[i], _np.conjugate(BVec[j])) opMxInStdBasis += choiMx_unnorm[i, j] * BiBj if not isinstance(op_mx_basis, _Basis): op_mx_basis = _Basis.cast(op_mx_basis, N) # make sure op_mx_basis is a Basis; we'd like dimension to be N #project operation matrix so it acts only on the space given by the desired state space blocks opMxInStdBasis = _bt.resize_std_mx(opMxInStdBasis, 'contract', op_mx_basis.create_simple_equivalent('std'), op_mx_basis.create_equivalent('std')) #transform operation matrix into appropriate basis return _bt.change_basis(opMxInStdBasis, op_mx_basis.create_equivalent('std'), op_mx_basis)
def fast_jamiolkowski_iso_std(operation_mx, op_mx_basis): """ The corresponding Choi matrix in the standard basis that is normalized to have trace == 1. This routine *only* computes the case of the Choi matrix being in the standard (matrix unit) basis, but does so more quickly than :func:`jamiolkowski_iso` and so is particuarly useful when only the eigenvalues of the Choi matrix are needed. Parameters ---------- operation_mx : numpy array the operation matrix to compute Choi matrix of. op_mx_basis : Basis object The source and destination basis, respectively. Allowed values are Matrix-unit (std), Gell-Mann (gm), Pauli-product (pp), and Qutrit (qt) (or a custom basis object). Returns ------- numpy array the Choi matrix, normalized to have trace == 1, in the std basis. """ #first, get operation matrix into std basis operation_mx = _np.asarray(operation_mx) op_mx_basis = _bt.create_basis_for_matrix(operation_mx, op_mx_basis) opMxInStdBasis = _bt.change_basis(operation_mx, op_mx_basis, op_mx_basis.create_equivalent('std')) #expand operation matrix so it acts on entire space of dmDim x dmDim density matrices opMxInStdBasis = _bt.resize_std_mx(opMxInStdBasis, 'expand', op_mx_basis.create_equivalent('std'), op_mx_basis.create_simple_equivalent('std')) #Shuffle indices to go from process matrix to Jamiolkowski matrix (they vectorize differently) N2 = opMxInStdBasis.shape[0]; N = int(_np.sqrt(N2)) assert(N * N == N2) # make sure N2 is a perfect square Jmx = opMxInStdBasis.reshape((N, N, N, N)) Jmx = _np.swapaxes(Jmx, 1, 2).flatten() Jmx = Jmx.reshape((N2, N2)) # This construction results in a Jmx with trace == dim(H) = sqrt(gateMxInPauliBasis.shape[0]) # but we'd like a Jmx with trace == 1, so normalize: Jmx_norm = Jmx / N return Jmx_norm
def checkBasis(self, cmb): #Op with Jamio map on gate in std and gm bases Jmx1 = j.jamiolkowski_iso(self.testGate, op_mx_basis=self.stdSmall, choi_mx_basis=cmb) Jmx2 = j.jamiolkowski_iso(self.testGateGM_mx, op_mx_basis=self.gmSmall, choi_mx_basis=cmb) #print("Jmx1.shape = ", Jmx1.shape) #Make sure these yield the same trace == 1 matrix self.assertArraysAlmostEqual(Jmx1, Jmx2) self.assertAlmostEqual(np.trace(Jmx1), 1.0) #Op on expanded gate in std and gm bases JmxExp1 = j.jamiolkowski_iso(self.expTestGate_mx, op_mx_basis=self.std, choi_mx_basis=cmb) JmxExp2 = j.jamiolkowski_iso(self.expTestGateGM_mx, op_mx_basis=self.gm, choi_mx_basis=cmb) #print("JmxExp1.shape = ", JmxExp1.shape) #Make sure these are the same as operating on the contracted basis self.assertArraysAlmostEqual(Jmx1, JmxExp1) self.assertArraysAlmostEqual(Jmx1, JmxExp2) #Reverse transform should yield back the operation matrix revTestGate_mx = j.jamiolkowski_iso_inv(Jmx1, choi_mx_basis=cmb, op_mx_basis=self.gmSmall) self.assertArraysAlmostEqual(revTestGate_mx, self.testGateGM_mx) #Reverse transform without specifying stateSpaceDims, then contraction, should yield same result revExpTestGate_mx = j.jamiolkowski_iso_inv(Jmx1, choi_mx_basis=cmb, op_mx_basis=self.std) self.assertArraysAlmostEqual( bt.resize_std_mx(revExpTestGate_mx, 'contract', self.std, self.stdSmall), self.testGate)
def fast_jamiolkowski_iso_std_inv(choi_mx, op_mx_basis): """ Given a choi matrix in the standard basis, return the corresponding operation matrix. This function performs the inverse of :function:`fast_jamiolkowski_iso_std`. Parameters ---------- choi_mx : numpy array the Choi matrix in the standard (matrix units) basis, normalized to have trace == 1, to compute operation matrix for. op_mx_basis : Basis object The source and destination basis, respectively. Allowed values are Matrix-unit (std), Gell-Mann (gm), Pauli-product (pp), and Qutrit (qt) (or a custom basis object). Returns ------- numpy array operation matrix in the desired basis. """ #Shuffle indices to go from process matrix to Jamiolkowski matrix (they vectorize differently) N2 = choi_mx.shape[0]; N = int(_np.sqrt(N2)) assert(N * N == N2) # make sure N2 is a perfect square opMxInStdBasis = choi_mx.reshape((N, N, N, N)) * N opMxInStdBasis = _np.swapaxes(opMxInStdBasis, 1, 2).flatten() opMxInStdBasis = opMxInStdBasis.reshape((N2, N2)) op_mx_basis = _bt.create_basis_for_matrix(opMxInStdBasis, op_mx_basis) #project operation matrix so it acts only on the space given by the desired state space blocks opMxInStdBasis = _bt.resize_std_mx(opMxInStdBasis, 'contract', op_mx_basis.create_simple_equivalent('std'), op_mx_basis.create_equivalent('std')) #transform operation matrix into appropriate basis return _bt.change_basis(opMxInStdBasis, op_mx_basis.create_equivalent('std'), op_mx_basis)
def test_general(self): Basis('pp', 2) Basis('std', [2, 1]) Basis([('std', 2), ('gm', 2)]) std = Basis('std', 2) std4 = Basis('std', 4) std2x2 = Basis([('std', 2), ('std', 2)]) gm = Basis('gm', 2) ungm = Basis('gm_unnormalized', 2) empty = Basis([]) #special "empty" basis self.assertEqual(empty.name, "*Empty*") from_basis,to_basis = pygsti.tools.build_basis_pair(np.identity(4,'d'),"std","gm") from_basis,to_basis = pygsti.tools.build_basis_pair(np.identity(4,'d'),std,"gm") from_basis,to_basis = pygsti.tools.build_basis_pair(np.identity(4,'d'),"std",gm) gm_mxs = gm.get_composite_matrices() unnorm = Basis(matrices=[ gm_mxs[0], 2*gm_mxs[1] ]) std[0] std.get_sub_basis_matrices(0) print(gm.get_composite_matrices()) self.assertTrue(gm.is_normalized()) self.assertFalse(ungm.is_normalized()) self.assertFalse(unnorm.is_normalized()) transMx = bt.transform_matrix(std, gm) composite = Basis([std, gm]) comp = Basis(matrices=[std, gm], name='comp', longname='CustomComposite') comp.labels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] comp = Basis(matrices=[std, gm], name='comp', longname='CustomComposite', labels=[ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H' ]) std2x2Matrices = np.array([ [[1, 0], [0, 0]], [[0, 1], [0, 0]], [[0, 0], [1, 0]], [[0, 0], [0, 1]] ],'complex') empty = Basis(matrices=[]) alt_standard = Basis(matrices=std2x2Matrices) print("MXS = \n",alt_standard._matrices) alt_standard = Basis(matrices=std2x2Matrices, name='std', longname='Standard' ) self.assertEqual(alt_standard, std2x2Matrices) mx = np.array([ [1, 0, 0, 1], [0, 1, 2, 0], [0, 2, 1, 0], [1, 0, 0, 1] ]) bt.change_basis(mx, 'std', 'gm') # shortname lookup bt.change_basis(mx, std, gm) # object bt.change_basis(mx, std, 'gm') # combination bt.flexible_change_basis(mx, std, gm) #same dimension I2x2 = np.identity(8,'d') I4 = bt.flexible_change_basis(I2x2, std2x2, std4) self.assertArraysAlmostEqual(bt.flexible_change_basis(I4, std4, std2x2), I2x2) with self.assertRaises(ValueError): bt.change_basis(mx, std, std4) # basis size mismatch mxInStdBasis = np.array([[1,0,0,2], [0,0,0,0], [0,0,0,0], [3,0,0,4]],'d') begin = Basis('std', [1,1]) end = Basis('std', 2) mxInReducedBasis = bt.resize_std_mx(mxInStdBasis, 'contract', end, begin) original = bt.resize_std_mx(mxInReducedBasis, 'expand', begin, end)
def jamiolkowski_iso(operation_mx, op_mx_basis='pp', choi_mx_basis='pp'): """ Given a operation matrix, return the corresponding Choi matrix that is normalized to have trace == 1. Parameters ---------- operation_mx : numpy array the operation matrix to compute Choi matrix of. op_mx_basis : Basis object The source and destination basis, respectively. Allowed values are Matrix-unit (std), Gell-Mann (gm), Pauli-product (pp), and Qutrit (qt) (or a custom basis object). choi_mx_basis : Basis object The source and destination basis, respectively. Allowed values are Matrix-unit (std), Gell-Mann (gm), Pauli-product (pp), and Qutrit (qt) (or a custom basis object). Returns ------- numpy array the Choi matrix, normalized to have trace == 1, in the desired basis. """ operation_mx = _np.asarray(operation_mx) op_mx_basis = _bt.create_basis_for_matrix(operation_mx, op_mx_basis) opMxInStdBasis = _bt.change_basis(operation_mx, op_mx_basis, op_mx_basis.create_equivalent('std')) #expand operation matrix so it acts on entire space of dmDim x dmDim density matrices # so that we can take dot products with the BVec matrices below opMxInStdBasis = _bt.resize_std_mx(opMxInStdBasis, 'expand', op_mx_basis.create_equivalent( 'std'), op_mx_basis.create_simple_equivalent('std')) N = opMxInStdBasis.shape[0] # dimension of the full-basis (expanded) gate dmDim = int(round(_np.sqrt(N))) # density matrix dimension #Note: we need to use the *full* basis of Matrix Unit, Gell-Mann, or Pauli-product matrices when # generating the Choi matrix, even when the original operation matrix doesn't include the entire basis. # This is because even when the original operation matrix doesn't include a certain basis element (B0 say), # conjugating with this basis element and tracing, i.e. trace(B0^dag * Operation * B0), is not necessarily zero. #get full list of basis matrices (in std basis) -- i.e. we use dmDim if not isinstance(choi_mx_basis, _Basis): choi_mx_basis = _Basis.cast(choi_mx_basis, N) # we'd like a basis of dimension N BVec = choi_mx_basis.create_simple_equivalent().elements M = len(BVec) # can be < N if basis has multiple block dims assert(M == N), 'Expected {}, got {}'.format(M, N) choiMx = _np.empty((N, N), 'complex') for i in range(M): for j in range(M): BiBj = _np.kron(BVec[i], _np.conjugate(BVec[j])) BiBj_dag = _np.transpose(_np.conjugate(BiBj)) choiMx[i, j] = _mt.trace(_np.dot(opMxInStdBasis, BiBj_dag)) \ / _mt.trace(_np.dot(BiBj, BiBj_dag)) # This construction results in a Jmx with trace == dim(H) = sqrt(operation_mx.shape[0]) # (dimension of density matrix) but we'd like a Jmx with trace == 1, so normalize: choiMx_normalized = choiMx / dmDim return choiMx_normalized