def _subsystem_apply_reference(state, channel, mask): if isket(state): state = ket2dm(state) if isoper(channel): full_oper = tensor([channel if mask[j] else qeye(state.dims[0][j]) for j in range(len(state.dims[0]))]) return full_oper * state * full_oper.dag() else: # Go to Choi, then Kraus # chan_mat = array(channel.data.todense()) choi_matrix = super_to_choi(channel) vals, vecs = eig(choi_matrix.full()) vecs = list(map(array, zip(*vecs))) kraus_list = [sqrt(vals[j]) * vec2mat(vecs[j]) for j in range(len(vals))] # Kraus operators to be padded with identities: k_qubit_kraus_list = product(kraus_list, repeat=sum(mask)) rho_out = Qobj(inpt=zeros(state.shape), dims=state.dims) for operator_iter in k_qubit_kraus_list: operator_iter = iter(operator_iter) op_iter_list = [next(operator_iter).conj().T if mask[j] else qeye(state.dims[0][j]) for j in range(len(state.dims[0]))] full_oper = tensor(list(map(Qobj, op_iter_list))) rho_out = rho_out + full_oper * state * full_oper.dag() return Qobj(rho_out)
def _jc_liouvillian(N): from qutip.tensor import tensor from qutip.operators import destroy, qeye from qutip.superoperator import liouvillian wc = 1.0 * 2 * np.pi # cavity frequency wa = 1.0 * 2 * np.pi # atom frequency g = 0.05 * 2 * np.pi # coupling strength kappa = 0.005 # cavity dissipation rate gamma = 0.05 # atom dissipation rate n_th_a = 1 # temperature in frequency units use_rwa = 0 # operators a = tensor(destroy(N), qeye(2)) sm = tensor(qeye(N), destroy(2)) # Hamiltonian if use_rwa: H = wc * a.dag() * a + wa * sm.dag() * sm + g * (a.dag() * sm + a * sm.dag()) else: H = wc * a.dag() * a + wa * sm.dag() * sm + g * (a.dag() + a) * (sm + sm.dag()) c_op_list = [] rate = kappa * (1 + n_th_a) if rate > 0.0: c_op_list.append(np.sqrt(rate) * a) rate = kappa * n_th_a if rate > 0.0: c_op_list.append(np.sqrt(rate) * a.dag()) rate = gamma if rate > 0.0: c_op_list.append(np.sqrt(rate) * sm) return liouvillian(H, c_op_list)
def test_sp_bandwidth(): "Sparse: Bandwidth" # Bandwidth test 1 A = create(25)+destroy(25)+qeye(25) band = sp_bandwidth(A.data) assert_equal(band[0], 3) assert_equal(band[1] == band[2] == 1, 1) # Bandwidth test 2 A = np.array([[1, 0, 0, 0, 1, 0, 0, 0], [0, 1, 1, 0, 0, 1, 0, 1], [0, 1, 1, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0, 1, 0], [1, 0, 1, 0, 1, 0, 0, 0], [0, 1, 0, 0, 0, 1, 0, 1], [0, 0, 0, 1, 0, 0, 1, 0], [0, 1, 0, 0, 0, 1, 0, 1]], dtype=np.int32) A = sp.csr_matrix(A) out1 = sp_bandwidth(A) assert_equal(out1[0], 13) assert_equal(out1[1] == out1[2] == 6, 1) # Bandwidth test 3 perm = reverse_cuthill_mckee(A) B = sp_permute(A, perm, perm) out2 = sp_bandwidth(B) assert_equal(out2[0], 5) assert_equal(out2[1] == out2[2] == 2, 1) # Asymmetric bandwidth test A = destroy(25)+qeye(25) out1 = sp_bandwidth(A.data) assert_equal(out1[0], 2) assert_equal(out1[1], 0) assert_equal(out1[2], 1)
def test_unitarity_known(): """ Metrics: Unitarity for known cases. """ def case(q_oper, known_unitarity): assert_almost_equal(unitarity(q_oper), known_unitarity) yield case, to_super(sigmax()), 1.0 yield case, sum(map( to_super, [qeye(2), sigmax(), sigmay(), sigmaz()] )) / 4, 0.0 yield case, sum(map( to_super, [qeye(2), sigmax()] )) / 2, 1 / 3.0
def test_rand_unitary_haar_unitarity(): """ Random Qobjs: Tests that unitaries are actually unitary. """ U = rand_unitary_haar(5) I = qeye(5) assert_(U * U.dag() == I)
def test_stinespring_dims(self, dimension): """ Stinespring: Check that dims of channels are preserved. """ chan = super_tensor(to_super(sigmax()), to_super(qeye(dimension))) A, B = to_stinespring(chan) assert A.dims == [[2, dimension, 1], [2, dimension]] assert B.dims == [[2, dimension, 1], [2, dimension]]
def test_stinespring_dims(self): """ Stinespring: Check that dims of channels are preserved. """ # FIXME: not the most general test, since this assumes a map # from square matrices to square matrices on the same space. chan = super_tensor(to_super(sigmax()), to_super(qeye(3))) A, B = to_stinespring(chan) assert_equal(A.dims, [[2, 3, 1], [2, 3]]) assert_equal(B.dims, [[2, 3, 1], [2, 3]])
def _prop_identity(self, U, tol=1e-6): """ Returns True if and only if U is proportional to the identity. """ if U[0, 0] != 0: norm_U = U / U[0, 0] return (qeye(U.dims[0]) - norm_U).norm() <= tol else: return False
def _powers(op, N): """ Generator that yields powers of an operator `op`, through to `N`. """ acc = qeye(op.dims[0]) yield acc for _ in range(N - 1): acc *= op yield acc
def _opto_liouvillian(N): from qutip.tensor import tensor from qutip.operators import destroy, qeye from qutip.superoperator import liouvillian Nc = 5 # Number of cavity states Nm = N # Number of mech states kappa = 0.3 # Cavity damping rate E = 0.1 # Driving Amplitude g0 = 2.4*kappa # Coupling strength Qm = 1e4 # Mech quality factor gamma = 1/Qm # Mech damping rate n_th = 1 # Mech bath temperature delta = -0.43 # Detuning a = tensor(destroy(Nc), qeye(Nm)) b = tensor(qeye(Nc), destroy(Nm)) num_b = b.dag()*b num_a = a.dag()*a H = -delta*(num_a)+num_b+g0*(b.dag()+b)*num_a+E*(a.dag()+a) cc = np.sqrt(kappa)*a cm = np.sqrt(gamma*(1.0 + n_th))*b cp = np.sqrt(gamma*n_th)*b.dag() c_ops = [cc,cm,cp] return liouvillian(H, c_ops)
def _spin_hamiltonian(N): from qutip.tensor import tensor from qutip.operators import qeye, sigmax, sigmay, sigmaz # array of spin energy splittings and coupling strengths. here we use # uniform parameters, but in general we don't have too h = 1.0 * 2 * np.pi * np.ones(N) Jz = 0.1 * 2 * np.pi * np.ones(N) Jx = 0.1 * 2 * np.pi * np.ones(N) Jy = 0.1 * 2 * np.pi * np.ones(N) # dephasing rate gamma = 0.01 * np.ones(N) si = qeye(2) sx = sigmax() sy = sigmay() sz = sigmaz() sx_list = [] sy_list = [] sz_list = [] for n in range(N): op_list = [] for m in range(N): op_list.append(si) op_list[n] = sx sx_list.append(tensor(op_list)) op_list[n] = sy sy_list.append(tensor(op_list)) op_list[n] = sz sz_list.append(tensor(op_list)) # construct the hamiltonian H = 0 # energy splitting terms for n in range(N): H += - 0.5 * h[n] * sz_list[n] # interaction terms for n in range(N-1): H += - 0.5 * Jx[n] * sx_list[n] * sx_list[n+1] H += - 0.5 * Jy[n] * sy_list[n] * sy_list[n+1] H += - 0.5 * Jz[n] * sz_list[n] * sz_list[n+1] return H
def test_QobjUnitaryOper(): "Qobj unitarity" # Check some standard operators Sx = sigmax() Sy = sigmay() assert_unitarity(qeye(4), True, "qeye(4) should be unitary.") assert_unitarity(Sx, True, "sigmax() should be unitary.") assert_unitarity(Sy, True, "sigmax() should be unitary.") assert_unitarity(sigmam(), False, "sigmam() should NOT be unitary.") assert_unitarity(destroy(10), False, "destroy(10) should NOT be unitary.") # Check multiplcation of unitary is unitary assert_unitarity(Sx*Sy, True, "sigmax()*sigmay() should be unitary.") # Check some other operations clear unitarity assert_unitarity(Sx+Sy, False, "sigmax()+sigmay() should NOT be unitary.") assert_unitarity(4*Sx, False, "4*sigmax() should NOT be unitary.") assert_unitarity(Sx*4, False, "sigmax()*4 should NOT be unitary.") assert_unitarity(4+Sx, False, "4+sigmax() should NOT be unitary.") assert_unitarity(Sx+4, False, "sigmax()+4 should NOT be unitary.")
def test_fidelity_known_cases(): """ Metrics: Checks fidelity against known cases. """ ket0 = basis(2, 0) ket1 = basis(2, 1) ketp = (ket0 + ket1).unit() # A state that almost overlaps with |+> should serve as # a nice test case, especially since we can analytically # calculate its overlap with |+>. ketpy = (ket0 + np.exp(1j * np.pi / 4) * ket1).unit() mms = qeye(2).unit() assert_almost_equal(fidelity(ket0, ketp), 1 / np.sqrt(2)) assert_almost_equal(fidelity(ket0, ket1), 0) assert_almost_equal(fidelity(ket0, mms), 1 / np.sqrt(2)) assert_almost_equal(fidelity(ketp, ketpy), np.sqrt( (1 / 8) + (1 / 2 + 1 / (2 * np.sqrt(2))) ** 2 ) )
def _spectrum_pi(H, wlist, c_ops, a_op, b_op, use_pinv=False): """ Internal function for calculating the spectrum of the correlation function :math:`\left<A(\\tau)B(0)\\right>`. """ L = H if issuper(H) else liouvillian(H, c_ops) tr_mat = tensor([qeye(n) for n in L.dims[0][0]]) N = np.prod(L.dims[0][0]) A = L.full() b = spre(b_op).full() a = spre(a_op).full() tr_vec = np.transpose(mat2vec(tr_mat.full())) rho_ss = steadystate(L) rho = np.transpose(mat2vec(rho_ss.full())) I = np.identity(N * N) P = np.kron(np.transpose(rho), tr_vec) Q = I - P spectrum = np.zeros(len(wlist)) for idx, w in enumerate(wlist): if use_pinv: MMR = np.linalg.pinv(-1.0j * w * I + A) else: MMR = np.dot(Q, np.linalg.solve(-1.0j * w * I + A, Q)) s = np.dot(tr_vec, np.dot(a, np.dot(MMR, np.dot(b, np.transpose(rho))))) spectrum[idx] = -2 * np.real(s[0, 0]) return spectrum
def test_chi_known(self): """ Superoperator: Chi-matrix for known cases is correct. """ def case(S, chi_expected, silent=True): chi_actual = to_chi(S) chiq = Qobj(chi_expected, dims=[[[2], [2]], [[2], [2]]], superrep='chi') if not silent: print(chi_actual) print(chi_expected) assert_almost_equal((chi_actual - chiq).norm('tr'), 0) yield case, sigmax(), [ [0, 0, 0, 0], [0, 4, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ] yield case, to_super(sigmax()), [ [0, 0, 0, 0], [0, 4, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ] yield case, qeye(2), [ [4, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ] yield case, (-1j * sigmax() * pi / 4).expm(), [ [2, 2j, 0, 0], [-2j, 2, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ]
def test_dnorm_qubit_known_cases(): """ Metrics: check agreement for known qubit channels. """ def case(chan1, chan2, expected, significant=4): # We again take a generous tolerance so that we don't kill off # SCS solvers. assert_approx_equal( dnorm(chan1, chan2), expected, significant=significant ) id_chan = to_choi(qeye(2)) S_eye = to_super(id_chan) X_chan = to_choi(sigmax()) depol = to_choi(Qobj( diag(ones((4,))), dims=[[[2], [2]], [[2], [2]]], superrep='chi' )) S_H = to_super(hadamard_transform()) W = swap() # We need to restrict the number of iterations for things on the boundary, # such as perfectly distinguishable channels. yield case, id_chan, X_chan, 2 yield case, id_chan, depol, 1.5 # Next, we'll generate some test cases based on comparisons to pre-existing # dnorm() implementations. In particular, the targets for the following # test cases were generated using QuantumUtils for MATLAB (https://goo.gl/oWXhO9). def overrotation(x): return to_super((1j * np.pi * x * sigmax() / 2).expm()) for x, target in { 1.000000e-03: 3.141591e-03, 3.100000e-03: 9.738899e-03, 1.000000e-02: 3.141463e-02, 3.100000e-02: 9.735089e-02, 1.000000e-01: 3.128689e-01, 3.100000e-01: 9.358596e-01 }.items(): yield case, overrotation(x), id_chan, target def had_mixture(x): return (1 - x) * S_eye + x * S_H for x, target in { 1.000000e-03: 2.000000e-03, 3.100000e-03: 6.200000e-03, 1.000000e-02: 2.000000e-02, 3.100000e-02: 6.200000e-02, 1.000000e-01: 2.000000e-01, 3.100000e-01: 6.200000e-01 }.items(): yield case, had_mixture(x), id_chan, target def swap_map(x): S = (1j * x * W).expm() S._type = None S.dims = [[[2], [2]], [[2], [2]]] S.superrep = 'super' return S for x, target in { 1.000000e-03: 2.000000e-03, 3.100000e-03: 6.199997e-03, 1.000000e-02: 1.999992e-02, 3.100000e-02: 6.199752e-02, 1.000000e-01: 1.999162e-01, 3.100000e-01: 6.173918e-01 }.items(): yield case, swap_map(x), id_chan, target # Finally, we add a known case from Johnston's QETLAB documentation, # || Phi - I ||,_♢ where Phi(X) = UXU⁺ and U = [[1, 1], [-1, 1]] / sqrt(2). yield case, Qobj([[1, 1], [-1, 1]]) / np.sqrt(2), qeye(2), np.sqrt(2)
def propagator(H, t, c_op_list=[], args={}, options=None, unitary_mode='batch', parallel=False, progress_bar=None, **kwargs): """ 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. unitary_mode = str ('batch', 'single') Solve all basis vectors simulaneously ('batch') or individually ('single'). parallel : bool {False, True} Run the propagator in parallel mode. This will override the unitary_mode settings if set to True. 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)`. """ kw = _default_kwargs() if 'num_cpus' in kwargs: num_cpus = kwargs['num_cpus'] else: num_cpus = kw['num_cpus'] 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 td_type = _td_format_check(H, c_op_list, solver='me') 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 if parallel: unitary_mode = 'single' u = np.zeros([N, N, len(tlist)], dtype=complex) output = parallel_map(_parallel_sesolve,range(N), task_args=(N,H, tlist,args,options), progress_bar=progress_bar, num_cpus=num_cpus) for n in range(N): for k, t in enumerate(tlist): u[:, n, k] = output[n].states[k].full().T else: if unitary_mode == 'single': 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, _safe_mode=False) for k, t in enumerate(tlist): u[:, n, k] = output.states[k].full().T progress_bar.finished() elif unitary_mode =='batch': u = np.zeros(len(tlist), dtype=object) _rows = np.array([(N+1)*m for m in range(N)]) _cols = np.zeros_like(_rows) _data = np.ones_like(_rows,dtype=complex) psi0 = Qobj(sp.coo_matrix((_data,(_rows,_cols))).tocsr()) if td_type[1] > 0 or td_type[2] > 0: H2 = [] for k in range(len(H)): if isinstance(H[k], list): H2.append([tensor(qeye(N), H[k][0]), H[k][1]]) else: H2.append(tensor(qeye(N), H[k])) else: H2 = tensor(qeye(N), H) output = sesolve(H2, psi0, tlist, [] , args = args, _safe_mode=False, options=Options(normalize_output=False)) for k, t in enumerate(tlist): u[k] = sp_reshape(output.states[k].data, (N, N)) unit_row_norm(u[k].data, u[k].indptr, u[k].shape[0]) u[k] = u[k].T.tocsr() else: raise Exception('Invalid unitary mode.') elif len(c_op_list) == 0 and H0.issuper: # calculate the propagator for the vector representation of the # density matrix (a superoperator propagator) unitary_mode = 'single' N = H0.shape[0] sqrt_N = int(np.sqrt(N)) dims = H0.dims u = np.zeros([N, N, len(tlist)], dtype=complex) if parallel: output = parallel_map(_parallel_mesolve,range(N * N), task_args=(sqrt_N,H,tlist,c_op_list,args,options), progress_bar=progress_bar, num_cpus=num_cpus) for n in range(N * N): for k, t in enumerate(tlist): u[:, n, k] = mat2vec(output[n].states[k].full()).T else: progress_bar.start(N) for n in range(0, N): progress_bar.update(n) col_idx, row_idx = np.unravel_index(n,(sqrt_N,sqrt_N)) rho0 = Qobj(sp.csr_matrix(([1],([row_idx],[col_idx])), shape=(sqrt_N,sqrt_N), dtype=complex)) output = mesolve(H, rho0, tlist, [], [], args, options, _safe_mode=False) 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) unitary_mode = 'single' N = H0.shape[0] dims = [H0.dims, H0.dims] u = np.zeros([N * N, N * N, len(tlist)], dtype=complex) if parallel: output = parallel_map(_parallel_mesolve,range(N * N), task_args=(N,H,tlist,c_op_list,args,options), progress_bar=progress_bar, num_cpus=num_cpus) for n in range(N * N): for k, t in enumerate(tlist): u[:, n, k] = mat2vec(output[n].states[k].full()).T else: progress_bar.start(N * N) for n in range(N * N): progress_bar.update(n) col_idx, row_idx = np.unravel_index(n,(N,N)) rho0 = Qobj(sp.csr_matrix(([1],([row_idx],[col_idx])), shape=(N,N), dtype=complex)) output = mesolve(H, rho0, tlist, c_op_list, [], args, options, _safe_mode=False) for k, t in enumerate(tlist): u[:, n, k] = mat2vec(output.states[k].full()).T progress_bar.finished() if len(tlist) == 2: if unitary_mode == 'batch': return Qobj(u[-1], dims=dims) else: return Qobj(u[:, :, 1], dims=dims) else: if unitary_mode == 'batch': return np.array([Qobj(u[k], dims=dims) for k in range(len(tlist))], dtype=object) else: return np.array([Qobj(u[:, :, k], dims=dims) for k in range(len(tlist))], dtype=object)
def test_QobjExpmZeroOper(): "Qobj expm zero_oper (#493)" A = Qobj(np.zeros((5,5), dtype=complex)) B = A.expm() assert_(B == qeye(5))
def spectrum_pi(H, wlist, c_ops, a_op, b_op, use_pinv=False): """ Calculate the spectrum corresponding to a correlation function :math:`\left<A(\\tau)B(0)\\right>`, i.e., the Fourier transform of the correlation function: .. math:: S(\omega) = \int_{-\infty}^{\infty} \left<A(\\tau)B(0)\\right> e^{-i\omega\\tau} d\\tau. Parameters ---------- H : :class:`qutip.qobj` system Hamiltonian. wlist : *list* / *array* list of frequencies for :math:`\\omega`. c_ops : list of :class:`qutip.qobj` list of collapse operators. a_op : :class:`qutip.qobj` operator A. b_op : :class:`qutip.qobj` operator B. Returns ------- s_vec: *array* An *array* with spectrum :math:`S(\omega)` for the frequencies specified in `wlist`. """ L = H if issuper(H) else liouvillian_fast(H, c_ops) tr_mat = tensor([qeye(n) for n in L.dims[0][0]]) N = prod(L.dims[0][0]) A = L.full() b = spre(b_op).full() a = spre(a_op).full() tr_vec = transpose(mat2vec(tr_mat.full())) rho_ss = steadystate(L) rho = transpose(mat2vec(rho_ss.full())) I = np.identity(N * N) P = np.kron(transpose(rho), tr_vec) Q = I - P s_vec = np.zeros(len(wlist)) for idx, w in enumerate(wlist): if use_pinv: MMR = numpy.linalg.pinv(-1.0j * w * I + A) else: MMR = np.dot(Q, np.linalg.solve(-1.0j * w * I + A, Q)) s = np.dot(tr_vec, np.dot(a, np.dot(MMR, np.dot(b, transpose(rho))))) s_vec[idx] = -2 * np.real(s[0, 0]) return s_vec
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 qudit_swap(dim): # We should likely generalize this and include it in qip.gates. W = qeye([dim, dim]) return tensor_swap(W, (0, 1))