def test_super_tensor_operket(): """ Tensor: Checks that super_tensor respects states. """ rho1, rho2 = rand_dm(5), rand_dm(7) operator_to_vector(rho1) operator_to_vector(rho2)
def case(S, n_trials=50): S = to_super(S) left_dims, right_dims = S.dims # Assume for the purposes of the test that S maps square operators to square operators. in_dim = np.prod(right_dims[0]) out_dim = np.prod(left_dims[0]) S_dual = to_super(S.dual_chan()) primals = [] duals = [] for idx_trial in range(n_trials): X = rand_dm_ginibre(out_dim) X.dims = left_dims X = operator_to_vector(X) Y = rand_dm_ginibre(in_dim) Y.dims = right_dims Y = operator_to_vector(Y) primals.append((X.dag() * S * Y)[0, 0]) duals.append((X.dag() * S_dual.dag() * Y)[0, 0]) np.testing.assert_array_almost_equal(primals, duals)
def test_call(): """ Test Qobj: Call """ # Make test objects. psi = rand_ket(3) rho = rand_dm_ginibre(3) U = rand_unitary(3) S = rand_super_bcsz(3) # Case 0: oper(ket). assert U(psi) == U * psi # Case 1: oper(oper). Should raise TypeError. with expect_exception(TypeError): U(rho) # Case 2: super(ket). assert S(psi) == vector_to_operator(S * operator_to_vector(ket2dm(psi))) # Case 3: super(oper). assert S(rho) == vector_to_operator(S * operator_to_vector(rho)) # Case 4: super(super). Should raise TypeError. with expect_exception(TypeError): S(S)
def test_dual_channel(sub_dimensions, n_trials=50): """ Qobj: dual_chan() preserves inner products with arbitrary density ops. """ S = rand_super_bcsz(np.prod(sub_dimensions)) S.dims = [[sub_dimensions, sub_dimensions], [sub_dimensions, sub_dimensions]] S = to_super(S) left_dims, right_dims = S.dims # Assume for the purposes of the test that S maps square operators to # square operators. in_dim = np.prod(right_dims[0]) out_dim = np.prod(left_dims[0]) S_dual = to_super(S.dual_chan()) primals = [] duals = [] for _ in [None] * n_trials: X = rand_dm_ginibre(out_dim) X.dims = left_dims X = operator_to_vector(X) Y = rand_dm_ginibre(in_dim) Y.dims = right_dims Y = operator_to_vector(Y) primals.append((X.dag() * S * Y)[0, 0]) duals.append((X.dag() * S_dual.dag() * Y)[0, 0]) np.testing.assert_array_almost_equal(primals, duals)
def test_CheckMulType(): "Qobj multiplication type" # ket-bra and bra-ket multiplication psi = basis(5) dm = psi * psi.dag() assert_(dm.isoper) assert_(dm.isherm) nrm = psi.dag() * psi assert_equal(np.prod(nrm.shape), 1) assert_((abs(nrm) == 1)[0, 0]) # operator-operator multiplication H1 = rand_herm(3) H2 = rand_herm(3) out = H1 * H2 assert_(out.isoper) out = H1 * H1 assert_(out.isoper) assert_(out.isherm) out = H2 * H2 assert_(out.isoper) assert_(out.isherm) U = rand_unitary(5) out = U.dag() * U assert_(out.isoper) assert_(out.isherm) N = num(5) out = N * N assert_(out.isoper) assert_(out.isherm) # operator-ket and bra-operator multiplication op = sigmax() ket1 = basis(2) ket2 = op * ket1 assert_(ket2.isket) bra1 = basis(2).dag() bra2 = bra1 * op assert_(bra2.isbra) assert_(bra2.dag() == ket2) # superoperator-operket and operbra-superoperator multiplication sop = to_super(sigmax()) opket1 = operator_to_vector(fock_dm(2)) opket2 = sop * opket1 assert(opket2.isoperket) opbra1 = operator_to_vector(fock_dm(2)).dag() opbra2 = opbra1 * sop assert(opbra2.isoperbra) assert_(opbra2.dag() == opket2)
def test_CheckMulType(): "Qobj multiplication type" # ket-bra and bra-ket multiplication psi = basis(5) dm = psi * psi.dag() assert dm.isoper assert dm.isherm nrm = psi.dag() * psi assert np.prod(nrm.shape) == 1 assert abs(nrm)[0, 0] == 1 # operator-operator multiplication H1 = rand_herm(3) H2 = rand_herm(3) out = H1 * H2 assert out.isoper out = H1 * H1 assert out.isoper assert out.isherm out = H2 * H2 assert out.isoper assert out.isherm U = rand_unitary(5) out = U.dag() * U assert out.isoper assert out.isherm N = num(5) out = N * N assert out.isoper assert out.isherm # operator-ket and bra-operator multiplication op = sigmax() ket1 = basis(2) ket2 = op * ket1 assert ket2.isket bra1 = basis(2).dag() bra2 = bra1 * op assert bra2.isbra assert bra2.dag() == ket2 # superoperator-operket and operbra-superoperator multiplication sop = to_super(sigmax()) opket1 = operator_to_vector(fock_dm(2)) opket2 = sop * opket1 assert opket2.isoperket opbra1 = operator_to_vector(fock_dm(2)).dag() opbra2 = opbra1 * sop assert opbra2.isoperbra assert opbra2.dag() == opket2
def test_QobjPermute(): "Qobj permute" A = basis(3, 0) B = basis(5, 4) C = basis(4, 2) psi = tensor(A, B, C) psi2 = psi.permute([2, 0, 1]) assert psi2 == tensor(C, A, B) psi_bra = psi.dag() psi2_bra = psi_bra.permute([2, 0, 1]) assert psi2_bra == tensor(C, A, B).dag() A = fock_dm(3, 0) B = fock_dm(5, 4) C = fock_dm(4, 2) rho = tensor(A, B, C) rho2 = rho.permute([2, 0, 1]) assert rho2 == tensor(C, A, B) for _ in range(3): A = rand_ket(3) B = rand_ket(4) C = rand_ket(5) psi = tensor(A, B, C) psi2 = psi.permute([1, 0, 2]) assert psi2 == tensor(B, A, C) psi_bra = psi.dag() psi2_bra = psi_bra.permute([1, 0, 2]) assert psi2_bra == tensor(B, A, C).dag() for _ in range(3): A = rand_dm(3) B = rand_dm(4) C = rand_dm(5) rho = tensor(A, B, C) rho2 = rho.permute([1, 0, 2]) assert rho2 == tensor(B, A, C) rho_vec = operator_to_vector(rho) rho2_vec = rho_vec.permute([[1, 0, 2], [4, 3, 5]]) assert rho2_vec == operator_to_vector(tensor(B, A, C)) rho_vec_bra = operator_to_vector(rho).dag() rho2_vec_bra = rho_vec_bra.permute([[1, 0, 2], [4, 3, 5]]) assert rho2_vec_bra == operator_to_vector(tensor(B, A, C)).dag() for _ in range(3): super_dims = [3, 5, 4] U = rand_unitary(np.prod(super_dims), density=0.02, dims=[super_dims, super_dims]) Unew = U.permute([2, 1, 0]) S_tens = to_super(U) S_tens_new = to_super(Unew) assert S_tens_new == S_tens.permute([[2, 1, 0], [5, 4, 3]])
def test_QobjPermute(): "Qobj permute" A = basis(3, 0) B = basis(5, 4) C = basis(4, 2) psi = tensor(A, B, C) psi2 = psi.permute([2, 0, 1]) assert_(psi2 == tensor(C, A, B)) psi_bra = psi.dag() psi2_bra = psi_bra.permute([2, 0, 1]) assert_(psi2_bra == tensor(C, A, B).dag()) A = fock_dm(3, 0) B = fock_dm(5, 4) C = fock_dm(4, 2) rho = tensor(A, B, C) rho2 = rho.permute([2, 0, 1]) assert_(rho2 == tensor(C, A, B)) for ii in range(3): A = rand_ket(3) B = rand_ket(4) C = rand_ket(5) psi = tensor(A, B, C) psi2 = psi.permute([1, 0, 2]) assert_(psi2 == tensor(B, A, C)) psi_bra = psi.dag() psi2_bra = psi_bra.permute([1, 0, 2]) assert_(psi2_bra == tensor(B, A, C).dag()) for ii in range(3): A = rand_dm(3) B = rand_dm(4) C = rand_dm(5) rho = tensor(A, B, C) rho2 = rho.permute([1, 0, 2]) assert_(rho2 == tensor(B, A, C)) rho_vec = operator_to_vector(rho) rho2_vec = rho_vec.permute([[1, 0, 2],[4,3,5]]) assert_(rho2_vec == operator_to_vector(tensor(B, A, C))) rho_vec_bra = operator_to_vector(rho).dag() rho2_vec_bra = rho_vec_bra.permute([[1, 0, 2],[4,3,5]]) assert_(rho2_vec_bra == operator_to_vector(tensor(B, A, C)).dag()) for ii in range(3): super_dims = [3, 5, 4] U = rand_unitary(np.prod(super_dims), density=0.02, dims=[super_dims, super_dims]) Unew = U.permute([2,1,0]) S_tens = to_super(U) S_tens_new = to_super(Unew) assert_(S_tens_new == S_tens.permute([[2,1,0],[5,4,3]]))
def test_QobjType(): "Qobj type" N = int(np.ceil(10.0 * np.random.random())) + 5 ket_data = np.random.random((N, 1)) ket_qobj = Qobj(ket_data) assert_equal(ket_qobj.type, 'ket') assert_(ket_qobj.isket) bra_data = np.random.random((1, N)) bra_qobj = Qobj(bra_data) assert_equal(bra_qobj.type, 'bra') assert_(bra_qobj.isbra) oper_data = np.random.random((N, N)) oper_qobj = Qobj(oper_data) assert_equal(oper_qobj.type, 'oper') assert_(oper_qobj.isoper) N = 9 super_data = np.random.random((N, N)) super_qobj = Qobj(super_data, dims=[[[3]], [[3]]]) assert_equal(super_qobj.type, 'super') assert_(super_qobj.issuper) operket_qobj = operator_to_vector(oper_qobj) assert_(operket_qobj.isoperket) operbra_qobj = operket_qobj.dag() assert_(operbra_qobj.isoperbra)
def test_QobjType(): "Qobj type" N = int(np.ceil(10.0 * np.random.random())) + 5 ket_data = np.random.random((N, 1)) ket_qobj = Qobj(ket_data) assert ket_qobj.type == 'ket' assert ket_qobj.isket bra_data = np.random.random((1, N)) bra_qobj = Qobj(bra_data) assert bra_qobj.type == 'bra' assert bra_qobj.isbra oper_data = np.random.random((N, N)) oper_qobj = Qobj(oper_data) assert oper_qobj.type == 'oper' assert oper_qobj.isoper N = 9 super_data = np.random.random((N, N)) super_qobj = Qobj(super_data, dims=[[[3]], [[3]]]) assert super_qobj.type == 'super' assert super_qobj.issuper operket_qobj = operator_to_vector(oper_qobj) assert operket_qobj.isoperket assert operket_qobj.dag().isoperbra
def test_composite_vec(): """ Composite: Tests compositing states and density operators. """ k1 = rand_ket(5) k2 = rand_ket(7) r1 = operator_to_vector(ket2dm(k1)) r2 = operator_to_vector(ket2dm(k2)) r3 = operator_to_vector(rand_dm(3)) r4 = operator_to_vector(rand_dm(4)) assert_(composite(k1, k2) == tensor(k1, k2)) assert_(composite(r3, r4) == super_tensor(r3, r4)) assert_(composite(k1, r4) == super_tensor(r1, r4)) assert_(composite(r3, k2) == super_tensor(r3, r2))
def composite(*args): """ Given two or more operators, kets or bras, returns the Qobj corresponding to a composite system over each argument. For ordinary operators and vectors, this is the tensor product, while for superoperators and vectorized operators, this is the column-reshuffled tensor product. If a mix of Qobjs supported on Hilbert and Liouville spaces are passed in, the former are promoted. Ordinary operators are assumed to be unitaries, and are promoted using ``to_super``, while kets and bras are promoted by taking their projectors and using ``operator_to_vector(ket2dm(arg))``. """ # First step will be to ensure everything is a Qobj at all. if not all(isinstance(arg, Qobj) for arg in args): raise TypeError("All arguments must be Qobjs.") # Next, figure out if we have something oper-like (isoper or issuper), # or something ket-like (isket or isoperket). Bra-like we'll deal with # by turning things into ket-likes and back. if all(map(_isoperlike, args)): # OK, we have oper/supers. if any(arg.issuper for arg in args): # Note that to_super does nothing to things # that are already type=super, while it will # promote unitaries to superunitaries. return super_tensor(*map(qutip.superop_reps.to_super, args)) else: # Everything's just an oper, so ordinary tensor products work. return tensor(*args) elif all(map(_isketlike, args)): # Ket-likes. if any(arg.isoperket for arg in args): # We have a vectorized operator, we we may need to promote # something. return super_tensor(*( arg if arg.isoperket else operator_to_vector(qutip.states.ket2dm(arg)) for arg in args )) else: # Everything's ordinary, so we can use the tensor product here. return tensor(*args) elif all(map(_isbralike, args)): # Turn into ket-likes and recurse. return composite(*(arg.dag() for arg in args)).dag() else: raise TypeError("Unsupported Qobj types [{}].".format( ", ".join(arg.type for arg in args) ))
def case(map, state): S = to_super(map) A, B = to_stinespring(map) q1 = vector_to_operator(S * operator_to_vector(state)) # FIXME: problem if Kraus index is implicitly # ptraced! q2 = (A * state * B.dag()).ptrace((0, )) assert_((q1 - q2).norm('tr') <= thresh)
def _pauli_basis(nq=1): # NOTE: This is slow as can be. # TODO: Make this sparse. CSR format was causing problems for the [idx, :] # slicing below. B = zeros((4**nq, 4**nq), dtype=complex) dims = [[[2]*nq]*2]*2 for idx, op in enumerate(starmap(tensor, product(_SINGLE_QUBIT_PAULI_BASIS, repeat=nq))): B[:, idx] = operator_to_vector(op).dag().data.todense() return Qobj(B, dims=dims)
def case(map, state): S = to_super(map) A, B = to_stinespring(map) q1 = vector_to_operator( S * operator_to_vector(state) ) # FIXME: problem if Kraus index is implicitly # ptraced! q2 = (A * state * B.dag()).ptrace((0,)) assert_((q1 - q2).norm('tr') <= thresh)
def _pauli_basis(nq=1): # NOTE: This is slow as can be. # TODO: Make this sparse. CSR format was causing problems for the [idx, :] # slicing below. B = zeros((4**nq, 4**nq), dtype=complex) dims = [[[2] * nq] * 2] * 2 for idx, op in enumerate( starmap(tensor, product(_SINGLE_QUBIT_PAULI_BASIS, repeat=nq))): B[:, idx] = operator_to_vector(op).dag().full() return Qobj(B, dims=dims)
def _sesolve_const(H, psi0, tlist, e_ops, args, opt, progress_bar): """ Evolve the wave function using an ODE solver """ if debug: print(inspect.stack()[0][3]) # # setup integrator. # if psi0.isket: initial_vector = psi0.full().ravel() L = -1.0j * H elif psi0.isunitary: initial_vector = operator_to_vector(psi0).full().ravel() L = -1.0j * spre(H) else: raise TypeError("The unitary solver requires psi0 to be" " a ket as initial state" " or a unitary as initial operator.") if opt.use_openmp and L.data.nnz >= qset.openmp_thresh: r = scipy.integrate.ode(cy_ode_rhs_openmp) r.set_f_params(L.data.data, L.data.indices, L.data.indptr, opt.openmp_threads) else: r = scipy.integrate.ode(cy_ode_rhs) r.set_f_params(L.data.data, L.data.indices, L.data.indptr) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) # # call generic ODE code # return _generic_ode_solve(r, psi0, tlist, e_ops, opt, progress_bar, dims=psi0.dims)
def test_stinespring_agrees(self, dimension): """ Stinespring: Partial Tr over pair agrees w/ supermatrix. """ map = rand_super_bcsz(dimension) state = rand_dm_ginibre(dimension) S = to_super(map) A, B = to_stinespring(map) q1 = vector_to_operator(S * operator_to_vector(state)) # FIXME: problem if Kraus index is implicitly # ptraced! q2 = (A * state * B.dag()).ptrace((0, )) assert (q1 - q2).norm('tr') <= tol
def propagator(H, t, c_op_list, args=None, options=None, sparse=False, progress_bar=None): """ Calculate the propagator U(t) for the density matrix or wave function such that :math:`\psi(t) = U(t)\psi(0)` or :math:`\\rho_{\mathrm vec}(t) = U(t) \\rho_{\mathrm vec}(0)` where :math:`\\rho_{\mathrm vec}` is the vector representation of the density matrix. Parameters ---------- H : qobj or list Hamiltonian as a Qobj instance of a nested list of Qobjs and coefficients in the list-string or list-function format for time-dependent Hamiltonians (see description in :func:`qutip.mesolve`). t : float or array-like Time or list of times for which to evaluate the propagator. c_op_list : list List of qobj collapse operators. args : list/array/dictionary Parameters to callback functions for time-dependent Hamiltonians and collapse operators. options : :class:`qutip.Options` with options for the ODE solver. progress_bar: BaseProgressBar Optional instance of BaseProgressBar, or a subclass thereof, for showing the progress of the simulation. By default no progress bar is used, and if set to True a TextProgressBar will be used. Returns ------- a : qobj Instance representing the propagator :math:`U(t)`. """ if progress_bar is None: progress_bar = BaseProgressBar() elif progress_bar is True: progress_bar = TextProgressBar() if options is None: options = Options() options.rhs_reuse = True rhs_clear() if isinstance(t, (int, float, np.integer, np.floating)): tlist = [0, t] else: tlist = t if isinstance( H, (types.FunctionType, types.BuiltinFunctionType, functools.partial)): H0 = H(0.0, args) elif isinstance(H, list): H0 = H[0][0] if isinstance(H[0], list) else H[0] else: H0 = H if len(c_op_list) == 0 and H0.isoper: # calculate propagator for the wave function N = H0.shape[0] dims = H0.dims u = np.zeros([N, N, len(tlist)], dtype=complex) progress_bar.start(N) for n in range(0, N): progress_bar.update(n) psi0 = basis(N, n) output = sesolve(H, psi0, tlist, [], args, options) for k, t in enumerate(tlist): u[:, n, k] = output.states[k].full().T progress_bar.finished() # todo: evolving a batch of wave functions: # psi_0_list = [basis(N, n) for n in range(N)] # psi_t_list = mesolve(H, psi_0_list, [0, t], [], [], args, options) # for n in range(0, N): # u[:,n] = psi_t_list[n][1].full().T elif len(c_op_list) == 0 and H0.issuper: # calculate the propagator for the vector representation of the # density matrix (a superoperator propagator) N = H0.shape[0] dims = H0.dims u = np.zeros([N, N, len(tlist)], dtype=complex) progress_bar.start(N) for n in range(0, N): progress_bar.update(n) psi0 = basis(N, n) rho0 = Qobj(vec2mat(psi0.full())) output = mesolve(H, rho0, tlist, [], [], args, options) for k, t in enumerate(tlist): u[:, n, k] = mat2vec(output.states[k].full()).T progress_bar.finished() else: # calculate the propagator for the vector representation of the # density matrix (a superoperator propagator) N = H0.shape[0] dims = [H0.dims, H0.dims] u = np.zeros([N * N, N * N, len(tlist)], dtype=complex) if sparse: progress_bar.start(N * N) for n in range(N * N): progress_bar.update(n) psi0 = basis(N * N, n) psi0.dims = [dims[0], 1] rho0 = vector_to_operator(psi0) output = mesolve(H, rho0, tlist, c_op_list, [], args, options) for k, t in enumerate(tlist): u[:, n, k] = operator_to_vector( output.states[k]).full(squeeze=True) progress_bar.finished() else: progress_bar.start(N * N) for n in range(N * N): progress_bar.update(n) psi0 = basis(N * N, n) rho0 = Qobj(vec2mat(psi0.full())) output = mesolve(H, rho0, tlist, c_op_list, [], args, options) for k, t in enumerate(tlist): u[:, n, k] = mat2vec(output.states[k].full()).T progress_bar.finished() if len(tlist) == 2: return Qobj(u[:, :, 1], dims=dims) else: return [Qobj(u[:, :, k], dims=dims) for k in range(len(tlist))]
#sigma Y control LC_y = liouvillian(Sy) #sigma Z control LC_z = liouvillian(Sz) E0 = sprepost(Si, Si) # target map 1 # E_targ = sprepost(had_gate, had_gate) # target map 2 E_targ = sprepost(Sx, Sx) psi0 = basis(2, 1) # ground state #psi0 = basis(2, 0) # excited state rho0 = ket2dm(psi0) print("rho0:\n{}\n".format(rho0)) rho0_vec = operator_to_vector(rho0) print("rho0_vec:\n{}\n".format(rho0_vec)) # target state 1 # psi_targ = (basis(2, 0) + basis(2, 1)).unit() # target state 2 #psi_targ = basis(2, 1) # ground state psi_targ = basis(2, 0) # excited state #psi_targ = psi0 rho_targ = ket2dm(psi_targ) print("rho_targ:\n{}\n".format(rho_targ)) rho_targ_vec = operator_to_vector(rho_targ) print("rho_targ_vec:\n{}\n".format(rho_targ_vec)) #print("L0:\n{}\n".format(L0))
def dnorm(A, B=None, solver="CVXOPT", verbose=False, force_solve=False): """ Calculates the diamond norm of the quantum map q_oper, using the simplified semidefinite program of [Wat12]_. The diamond norm SDP is solved by using CVXPY_. Parameters ---------- A : Qobj Quantum map to take the diamond norm of. B : Qobj or None If provided, the diamond norm of :math:`A - B` is taken instead. solver : str Solver to use with CVXPY. One of "CVXOPT" (default) or "SCS". The latter tends to be significantly faster, but somewhat less accurate. verbose : bool If True, prints additional information about the solution. force_solve : bool If True, forces dnorm to solve the associated SDP, even if a special case is known for the argument. Returns ------- dn : float Diamond norm of q_oper. Raises ------ ImportError If CVXPY cannot be imported. .. _cvxpy: http://www.cvxpy.org/en/latest/ """ if cvxpy is None: # pragma: no cover raise ImportError("dnorm() requires CVXPY to be installed.") # We follow the strategy of using Watrous' simpler semidefinite # program in its primal form. This is the same strategy used, # for instance, by both pyGSTi and SchattenNorms.jl. (By contrast, # QETLAB uses the dual problem.) # Check if A and B are both unitaries. If so, then we can without # loss of generality choose B to be the identity by using the # unitary invariance of the diamond norm, # || A - B ||_♢ = || A B⁺ - I ||_♢. # Then, using the technique mentioned by each of Johnston and # da Silva, # || A B⁺ - I ||_♢ = max_{i, j} | \lambda_i(A B⁺) - \lambda_j(A B⁺) |, # where \lambda_i(U) is the ith eigenvalue of U. if ( # There's a lot of conditions to check for this path. not force_solve and B is not None and # Only check if they aren't superoperators. A.type == "oper" and B.type == "oper" and # The difference of unitaries optimization is currently # only implemented for d == 2. Much of the code below is more general, # though, in anticipation of generalizing the optimization. A.shape[0] == 2 ): # Make an identity the same size as A and B to # compare against. I = qeye(A.dims[0]) # Compare to B first, so that an error is raised # as soon as possible. Bd = B.dag() if ( (B * Bd - I).norm() < 1e-6 and (A * A.dag() - I).norm() < 1e-6 ): # Now we are on the fast path, so let's compute the # eigenvalues, then find the diameter of the smallest circle # containing all of them. # # For now, this is only implemented for dim = 2, such that # generalizing here will allow for generalizing the optimization. # A reasonable approach would probably be to use Welzl's algorithm # (https://en.wikipedia.org/wiki/Smallest-circle_problem). U = A * B.dag() eigs = U.eigenenergies() eig_distances = np.abs(eigs[:, None] - eigs[None, :]) return np.max(eig_distances) # Force the input superoperator to be a Choi matrix. J = to_choi(A) if B is not None: J -= to_choi(B) # Watrous 2012 also points out that the diamond norm of Lambda # is the same as the completely-bounded operator-norm (∞-norm) # of the dual map of Lambda. We can evaluate that norm much more # easily if Lambda is completely positive, since then the largest # eigenvalue is the same as the largest singular value. if not force_solve and J.iscp: S_dual = to_super(J.dual_chan()) vec_eye = operator_to_vector(qeye(S_dual.dims[1][1])) op = vector_to_operator(S_dual * vec_eye) # The 2-norm was not implemented for sparse matrices as of the time # of this writing. Thus, we must yet again go dense. return la.norm(op.data.todense(), 2) # If we're still here, we need to actually solve the problem. # Assume square... dim = np.prod(J.dims[0][0]) # The constraints only depend on the dimension, so # we can cache them efficiently. problem, Jr, Ji, X, rho0, rho1 = dnorm_problem(dim) # Load the parameters with the Choi matrix passed in. J_dat = J.data Jr.value = sp.csr_matrix((J_dat.data.real, J_dat.indices, J_dat.indptr), shape=J_dat.shape) Ji.value = sp.csr_matrix((J_dat.data.imag, J_dat.indices, J_dat.indptr), shape=J_dat.shape) # Finally, set up and run the problem. problem.solve(solver=solver, verbose=verbose) return problem.value
def _sesolve_list_func_td(H_list, psi0, tlist, e_ops, args, opt, progress_bar): """ Internal function for solving the master equation. See mesolve for usage. """ if debug: print(inspect.stack()[0][3]) # # check initial state or oper # if psi0.isket: initial_vector = psi0.full().ravel() oper_evo = False elif psi0.isunitary: initial_vector = operator_to_vector(psi0).full().ravel() oper_evo = True else: raise TypeError("The unitary solver requires psi0 to be" " a ket as initial state" " or a unitary as initial operator.") # # construct liouvillian in list-function format # L_list = [] if not opt.rhs_with_state: constant_func = lambda x, y: 1.0 else: constant_func = lambda x, y, z: 1.0 # add all hamitonian terms to the lagrangian list for h_spec in H_list: if isinstance(h_spec, Qobj): h = h_spec h_coeff = constant_func elif isinstance(h_spec, list): h = h_spec[0] h_coeff = h_spec[1] else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected callback function)") if oper_evo: L = -1.0j * spre(h) else: L = -1j * h L_list.append([L.data, h_coeff]) L_list_and_args = [L_list, args] # # setup integrator # if oper_evo: initial_vector = operator_to_vector(psi0).full().ravel() else: initial_vector = psi0.full().ravel() if not opt.rhs_with_state: r = scipy.integrate.ode(psi_list_td) else: r = scipy.integrate.ode(psi_list_td_with_state) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) r.set_f_params(L_list_and_args) # # call generic ODE code # return _generic_ode_solve(r, psi0, tlist, e_ops, opt, progress_bar, dims=psi0.dims)
def dnorm(A, B=None, solver="CVXOPT", verbose=False, force_solve=False, sparse=True): """ Calculates the diamond norm of the quantum map q_oper, using the simplified semidefinite program of [Wat12]_. The diamond norm SDP is solved by using CVXPY_. Parameters ---------- A : Qobj Quantum map to take the diamond norm of. B : Qobj or None If provided, the diamond norm of :math:`A - B` is taken instead. solver : str Solver to use with CVXPY. One of "CVXOPT" (default) or "SCS". The latter tends to be significantly faster, but somewhat less accurate. verbose : bool If True, prints additional information about the solution. force_solve : bool If True, forces dnorm to solve the associated SDP, even if a special case is known for the argument. sparse : bool Whether to use sparse matrices in the convex optimisation problem. Default True. Returns ------- dn : float Diamond norm of q_oper. Raises ------ ImportError If CVXPY cannot be imported. .. _cvxpy: http://www.cvxpy.org/en/latest/ """ if cvxpy is None: # pragma: no cover raise ImportError("dnorm() requires CVXPY to be installed.") # We follow the strategy of using Watrous' simpler semidefinite # program in its primal form. This is the same strategy used, # for instance, by both pyGSTi and SchattenNorms.jl. (By contrast, # QETLAB uses the dual problem.) # Check if A and B are both unitaries. If so, then we can without # loss of generality choose B to be the identity by using the # unitary invariance of the diamond norm, # || A - B ||_♢ = || A B⁺ - I ||_♢. # Then, using the technique mentioned by each of Johnston and # da Silva, # || A B⁺ - I ||_♢ = max_{i, j} | \lambda_i(A B⁺) - \lambda_j(A B⁺) |, # where \lambda_i(U) is the ith eigenvalue of U. if ( # There's a lot of conditions to check for this path. not force_solve and B is not None and # Only check if they aren't superoperators. A.type == "oper" and B.type == "oper" and # The difference of unitaries optimization is currently # only implemented for d == 2. Much of the code below is more general, # though, in anticipation of generalizing the optimization. A.shape[0] == 2 ): # Make an identity the same size as A and B to # compare against. I = qeye(A.dims[0]) # Compare to B first, so that an error is raised # as soon as possible. Bd = B.dag() if ( (B * Bd - I).norm() < 1e-6 and (A * A.dag() - I).norm() < 1e-6 ): # Now we are on the fast path, so let's compute the # eigenvalues, then find the diameter of the smallest circle # containing all of them. # # For now, this is only implemented for dim = 2, such that # generalizing here will allow for generalizing the optimization. # A reasonable approach would probably be to use Welzl's algorithm # (https://en.wikipedia.org/wiki/Smallest-circle_problem). U = A * B.dag() eigs = U.eigenenergies() eig_distances = np.abs(eigs[:, None] - eigs[None, :]) return np.max(eig_distances) # Force the input superoperator to be a Choi matrix. J = to_choi(A) if B is not None: J -= to_choi(B) # Watrous 2012 also points out that the diamond norm of Lambda # is the same as the completely-bounded operator-norm (∞-norm) # of the dual map of Lambda. We can evaluate that norm much more # easily if Lambda is completely positive, since then the largest # eigenvalue is the same as the largest singular value. if not force_solve and J.iscp: S_dual = to_super(J.dual_chan()) vec_eye = operator_to_vector(qeye(S_dual.dims[1][1])) op = vector_to_operator(S_dual * vec_eye) # The 2-norm was not implemented for sparse matrices as of the time # of this writing. Thus, we must yet again go dense. return la.norm(op.data.todense(), 2) # If we're still here, we need to actually solve the problem. # Assume square... dim = np.prod(J.dims[0][0]) J_dat = J.data if not sparse: # The parameters and constraints only depend on the dimension, so # we can cache them efficiently. problem, Jr, Ji = dnorm_problem(dim) # Load the parameters with the Choi matrix passed in. Jr.value = sp.csr_matrix((J_dat.data.real, J_dat.indices, J_dat.indptr), shape=J_dat.shape).toarray() Ji.value = sp.csr_matrix((J_dat.data.imag, J_dat.indices, J_dat.indptr), shape=J_dat.shape).toarray() else: # The parameters do not depend solely on the dimension, # so we can not cache them efficiently. problem = dnorm_sparse_problem(dim, J_dat) problem.solve(solver=solver, verbose=verbose) return problem.value
def _sesolve_func_td(H_func, psi0, tlist, e_ops, args, opt, progress_bar): """! Evolve the wave function using an ODE solver with time-dependent Hamiltonian. """ if debug: print(inspect.stack()[0][3]) # # check initial state or oper # if psi0.isket: initial_vector = psi0.full().ravel() oper_evo = False elif psi0.isunitary: initial_vector = operator_to_vector(psi0).full().ravel() oper_evo = True else: raise TypeError("The unitary solver requires psi0 to be" " a ket as initial state" " or a unitary as initial operator.") # # setup integrator # new_args = None if type(args) is dict: new_args = {} for key in args: if isinstance(args[key], Qobj): new_args[key] = args[key].data else: new_args[key] = args[key] elif type(args) is list or type(args) is tuple: new_args = [] for arg in args: if isinstance(arg, Qobj): new_args.append(arg.data) else: new_args.append(arg) if type(args) is tuple: new_args = tuple(new_args) else: if isinstance(args, Qobj): new_args = args.data else: new_args = args if oper_evo: initial_vector = operator_to_vector(psi0).full().ravel() # Check that function returns superoperator if H_func(0, args).issuper: L_func = H_func else: L_func = lambda t, args: spre(H_func(t, args)) else: initial_vector = psi0.full().ravel() L_func = H_func if not opt.rhs_with_state: r = scipy.integrate.ode(cy_ode_psi_func_td) else: r = scipy.integrate.ode(cy_ode_psi_func_td_with_state) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) r.set_f_params(L_func, new_args) # # call generic ODE code # return _generic_ode_solve(r, psi0, tlist, e_ops, opt, progress_bar, dims=psi0.dims)
def propagator(H, t, c_op_list, args=None, options=None, sparse=False): """ Calculate the propagator U(t) for the density matrix or wave function such that :math:`\psi(t) = U(t)\psi(0)` or :math:`\\rho_{\mathrm vec}(t) = U(t) \\rho_{\mathrm vec}(0)` where :math:`\\rho_{\mathrm vec}` is the vector representation of the density matrix. Parameters ---------- H : qobj or list Hamiltonian as a Qobj instance of a nested list of Qobjs and coefficients in the list-string or list-function format for time-dependent Hamiltonians (see description in :func:`qutip.mesolve`). t : float or array-like Time or list of times for which to evaluate the propagator. c_op_list : list List of qobj collapse operators. args : list/array/dictionary Parameters to callback functions for time-dependent Hamiltonians and collapse operators. options : :class:`qutip.Options` with options for the ODE solver. Returns ------- a : qobj Instance representing the propagator :math:`U(t)`. """ if options is None: options = Options() options.rhs_reuse = True rhs_clear() if isinstance(t, (int, float, np.integer, np.floating)): tlist = [0, t] else: tlist = t if isinstance(H, (types.FunctionType, types.BuiltinFunctionType, functools.partial)): H0 = H(0.0, args) elif isinstance(H, list): H0 = H[0][0] if isinstance(H[0], list) else H[0] else: H0 = H if len(c_op_list) == 0 and H0.isoper: # calculate propagator for the wave function N = H0.shape[0] dims = H0.dims u = np.zeros([N, N, len(tlist)], dtype=complex) for n in range(0, N): psi0 = basis(N, n) output = sesolve(H, psi0, tlist, [], args, options) for k, t in enumerate(tlist): u[:, n, k] = output.states[k].full().T # todo: evolving a batch of wave functions: # psi_0_list = [basis(N, n) for n in range(N)] # psi_t_list = mesolve(H, psi_0_list, [0, t], [], [], args, options) # for n in range(0, N): # u[:,n] = psi_t_list[n][1].full().T elif len(c_op_list) == 0 and H0.issuper: # calculate the propagator for the vector representation of the # density matrix (a superoperator propagator) N = H0.shape[0] dims = H0.dims u = np.zeros([N, N, len(tlist)], dtype=complex) for n in range(0, N): psi0 = basis(N, n) rho0 = Qobj(vec2mat(psi0.full())) output = mesolve(H, rho0, tlist, [], [], args, options) for k, t in enumerate(tlist): u[:, n, k] = mat2vec(output.states[k].full()).T else: # calculate the propagator for the vector representation of the # density matrix (a superoperator propagator) N = H0.shape[0] dims = [H0.dims, H0.dims] u = np.zeros([N * N, N * N, len(tlist)], dtype=complex) if sparse: for n in range(N * N): psi0 = basis(N * N, n) psi0.dims = [dims[0], 1] rho0 = vector_to_operator(psi0) output = mesolve(H, rho0, tlist, c_op_list, [], args, options) for k, t in enumerate(tlist): u[:, n, k] = operator_to_vector( output.states[k]).full(squeeze=True) else: for n in range(N * N): psi0 = basis(N * N, n) rho0 = Qobj(vec2mat(psi0.full())) output = mesolve(H, rho0, tlist, c_op_list, [], args, options) for k, t in enumerate(tlist): u[:, n, k] = mat2vec(output.states[k].full()).T if len(tlist) == 2: return Qobj(u[:, :, 1], dims=dims) else: return [Qobj(u[:, :, k], dims=dims) for k in range(len(tlist))]
def _sesolve_list_str_td(H_list, psi0, tlist, e_ops, args, opt, progress_bar): """ Internal function for solving the master equation. See mesolve for usage. """ if debug: print(inspect.stack()[0][3]) if psi0.isket: oper_evo = False elif psi0.isunitary: oper_evo = True else: raise TypeError("The unitary solver requires psi0 to be" " a ket as initial state" " or a unitary as initial operator.") # # construct dynamics generator # Ldata = [] Linds = [] Lptrs = [] Lcoeff = [] Lobj = [] # loop over all hamiltonian terms, convert to superoperator form and # add the data of sparse matrix representation to h_coeff for h_spec in H_list: if isinstance(h_spec, Qobj): h = h_spec h_coeff = "1.0" elif isinstance(h_spec, list): h = h_spec[0] h_coeff = h_spec[1] else: raise TypeError("Incorrect specification of time-dependent " + "Hamiltonian (expected string format)") if oper_evo: L = -1.0j * spre(h) else: L = -1j * h Ldata.append(L.data.data) Linds.append(L.data.indices) Lptrs.append(L.data.indptr) if isinstance(h_coeff, Cubic_Spline): Lobj.append(h_coeff.coeffs) Lcoeff.append(h_coeff) # the total number of Hamiltonian terms n_L_terms = len(Ldata) # Check which components should use OPENMP omp_components = None if qset.has_openmp: if opt.use_openmp: omp_components = openmp_components(Lptrs) # # setup ode args string: we expand the list Ldata, Linds and Lptrs into # and explicit list of parameters # string_list = [] for k in range(n_L_terms): string_list.append("Ldata[%d], Linds[%d], Lptrs[%d]" % (k, k, k)) # Add object terms to end of ode args string for k in range(len(Lobj)): string_list.append("Lobj[%d]" % k) for name, value in args.items(): if isinstance(value, np.ndarray): string_list.append(name) else: string_list.append(str(value)) parameter_string = ",".join(string_list) # # generate and compile new cython code if necessary # if not opt.rhs_reuse or config.tdfunc is None: if opt.rhs_filename is None: config.tdname = "rhs" + str(os.getpid()) + str(config.cgen_num) else: config.tdname = opt.rhs_filename cgen = Codegen(h_terms=n_L_terms, h_tdterms=Lcoeff, args=args, config=config, use_openmp=opt.use_openmp, omp_components=omp_components, omp_threads=opt.openmp_threads) cgen.generate(config.tdname + ".pyx") code = compile('from ' + config.tdname + ' import cy_td_ode_rhs', '<string>', 'exec') exec(code, globals()) config.tdfunc = cy_td_ode_rhs # # setup integrator # if oper_evo: initial_vector = operator_to_vector(psi0).full().ravel() else: initial_vector = psi0.full().ravel() r = scipy.integrate.ode(config.tdfunc) r.set_integrator('zvode', method=opt.method, order=opt.order, atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps, first_step=opt.first_step, min_step=opt.min_step, max_step=opt.max_step) r.set_initial_value(initial_vector, tlist[0]) code = compile('r.set_f_params(' + parameter_string + ')', '<string>', 'exec') exec(code, locals(), args) # Remove RHS cython file if necessary if not opt.rhs_reuse and config.tdname: _cython_build_cleanup(config.tdname) # # call generic ODE code # return _generic_ode_solve(r, psi0, tlist, e_ops, opt, progress_bar, dims=psi0.dims)