qutip / qutip

QuTiP: Quantum Toolbox in Python
https://qutip.org
BSD 3-Clause "New" or "Revised" License
1.69k stars 635 forks source link

.dnorm() method on quantum channel objects cannot calculate non-trivial diamond norm distances between channels, instead raises ValueError #1422

Closed CharlieDerby closed 3 years ago

CharlieDerby commented 3 years ago

I have noticed that when I try to use the .dnorm() method to calculate the diamond norm distance between two channels I am greeted with an error and the operation fails. Rather than an error I expected the output to be a number between 0 and 2. See the simple example below.

from qutip import destroy, qeye, fock_dm, kraus_to_choi

# Choi matrix for 1 qubit amplitude damping channel with probability p
def AmpDampChoi(p):
    Kraus = [(1-p)**.5*qeye(2),p**.5*destroy(2),p**.5*fock_dm(2,0)]
    return kraus_to_choi(Kraus)

# Choi matrix for identity channel on 1 qubit
IdChoi = kraus_to_choi([qeye(2)])

# These values are found without issue but since they're all CPTP maps they all have dnorm 1
print(IdChoi.dnorm())
print(AmpDampChoi(0).dnorm())
print(AmpDampChoi(0.64).dnorm())

# These trivial diamond norm distances between a channel and itself are also produced
print(IdChoi.dnorm(AmpDampChoi(0)))
print(IdChoi.dnorm(IdChoi))
print(AmpDampChoi(0.5).dnorm(AmpDampChoi(0.5)))

# If a diamond norm distance between two different channels is sought then we see the error
print(IdChoi.dnorm(AmpDampChoi(0.5)))
print(AmpDampChoi(0.4).dnorm(AmpDampChoi(0.5)))

Attempting to run either of the final two prints gives the following spiel:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
TypeError: float() argument must be a string or a number, not 'csr_matrix'

The above exception was the direct cause of the following exception:

ValueError                                Traceback (most recent call last)
<ipython-input-21-add6d3e56036> in <module>
     20 
     21 # If a diamond norm distance between two different channels is sought then we see the error
---> 22 print(IdChoi.dnorm(AmpDampChoi(0.5)))
     23 print(AmpDampChoi(0.4).dnorm(AmpDampChoi(0.5)))

~/.local/lib/python3.8/site-packages/qutip/qobj.py in dnorm(self, B)
   1881 
   1882         """
-> 1883         return mts.dnorm(self, B)
   1884 
   1885     @property

~/.local/lib/python3.8/site-packages/qutip/metrics.py in dnorm(A, B, solver, verbose, force_solve)
    475                              shape=J_dat.shape)
    476     # Finally, set up and run the problem.
--> 477     problem.solve(solver=solver, verbose=verbose)
    478 
    479     return problem.value

~/.local/lib/python3.8/site-packages/cvxpy/problems/problem.py in solve(self, *args, **kwargs)
    394         else:
    395             solve_func = Problem._solve
--> 396         return solve_func(self, *args, **kwargs)
    397 
    398     @classmethod

~/.local/lib/python3.8/site-packages/cvxpy/problems/problem.py in _solve(self, solver, warm_start, verbose, gp, qcp, requires_grad, enforce_dpp, **kwargs)
    742                 return self.value
    743 
--> 744         data, solving_chain, inverse_data = self.get_problem_data(
    745             solver, gp, enforce_dpp)
    746         solution = solving_chain.solve_via_data(

~/.local/lib/python3.8/site-packages/cvxpy/problems/problem.py in get_problem_data(self, solver, gp, enforce_dpp)
    523             inverse_data = self._cache.inverse_data + [solver_inverse_data]
    524         else:
--> 525             data, inverse_data = solving_chain.apply(self)
    526             safe_to_cache = (
    527                 isinstance(data, dict)

~/.local/lib/python3.8/site-packages/cvxpy/reductions/chain.py in apply(self, problem)
     69         inverse_data = []
     70         for r in self.reductions:
---> 71             problem, inv = r.apply(problem)
     72             inverse_data.append(inv)
     73         return problem, inverse_data

~/.local/lib/python3.8/site-packages/cvxpy/reductions/solvers/conic_solvers/cvxopt_conif.py in apply(self, problem)
    110         len_eq = problem.cone_dims.zero
    111 
--> 112         c, d, A, b = problem.apply_parameters()
    113         data[s.C] = c
    114         inv_data[s.OFFSET] = d

~/.local/lib/python3.8/site-packages/cvxpy/reductions/dcp2cone/cone_matrix_stuffing.py in apply_parameters(self, id_to_param_value, zero_offset, keep_zeros)
    167             return (np.array(self.id_to_param[idx].value) if id_to_param_value
    168                     is None else id_to_param_value[idx])
--> 169         param_vec = canonInterface.get_parameter_vector(
    170             self.total_param_size,
    171             self.param_id_to_col,

~/.local/lib/python3.8/site-packages/cvxpy/cvxcore/python/canonInterface.py in get_parameter_vector(param_size, param_id_to_col, param_id_to_size, param_id_to_value_fn, zero_offset)
     55             value = param_id_to_value_fn(param_id).flatten(order='F')
     56             size = param_id_to_size[param_id]
---> 57             param_vec[col:col + size] = value
     58     return param_vec
     59 

ValueError: setting an array element with a sequence.

qutip.about() outputs the following info

QuTiP Version:      4.5.2
Numpy Version:      1.19.1
Scipy Version:      1.5.2
Cython Version:     0.29.21
Matplotlib Version: 3.3.1
Python Version:     3.8.2
Number of CPUs:     6
BLAS Info:          OPENBLAS
OPENMP Installed:   False
INTEL MKL Ext:      False
Platform Info:      Linux (x86_64)
Installation path:  /home/charlie/.local/lib/python3.8/site-packages/qutip
jakelishman commented 3 years ago

Looks like there were breaking changes introduced in CVXPY 1.1 that changed some sort of matrix handling? I think the entirely of the dnorm function was written by Chris Granade about 5 years ago, and they're off at Microsoft now.

As an immediate workaround, you can pin the version of CVXPY in conda to 1.0 (conda install 'cvxpy=1.0') to fix it. Otherwise, probably there's a solution in swapping over a load of * to @ in qutip/semidefinite.py and qutip/metrics.py, but that might be a bit nontrivial to solve. If you succeed, please do make a pull request.

The reason that the "simple" cases work is that QuTiP detects them as known results and has fast paths avoiding cvxpy.

MrRobot2211 commented 3 years ago

I think I have tracked down the problem to this issue https://github.com/cvxgrp/cvxpy/issues/1159, we could patch it using dense matrices instead of sparse ones.

jakelishman commented 3 years ago

Fixed in #1463.