def linearize(expr): """Returns the tangent approximation to the expression. Gives an elementwise lower (upper) bound for convex (concave) expressions. No guarantees for non-DCP expressions. Args: expr: An expression. Returns: An affine expression. """ if expr.is_affine(): return expr else: if expr.value is None: raise ValueError( "Cannot linearize non-affine expression with missing variable values." ) tangent = np.real(expr.value) #+ np.imag(expr.value) grad_map = expr.grad for var in expr.variables(): if grad_map[var] is None: return None complex_flag = False if var.is_complex() or np.any(np.iscomplex(grad_map[var])): complex_flag = True if var.ndim > 1: temp = cvx.reshape(cvx.vec(var - var.value), (var.shape[0] * var.shape[1], 1)) if complex_flag: flattened = np.transpose(np.real(grad_map[var])) @ cvx.real(temp) + \ np.transpose(np.imag(grad_map[var])) @ cvx.imag(temp) else: flattened = np.transpose(np.real(grad_map[var])) @ temp tangent = tangent + cvx.reshape(flattened, expr.shape) elif var.size > 1: if complex_flag: tangent = tangent + np.transpose(np.real(grad_map[var])) @ (cvx.real(var) - np.real(var.value)) \ + np.transpose(np.imag(grad_map[var])) @ (cvx.imag(var) - np.imag(var.value)) else: tangent = tangent + np.transpose(np.real( grad_map[var])) @ (var - var.value) else: if complex_flag: tangent = tangent + np.real(grad_map[var]) * (cvx.real(var) - np.real(var.value)) \ + np.imag(grad_map[var]) * (cvx.imag(var) - np.imag(var.value)) else: tangent = tangent + np.real( grad_map[var]) * (var - var.value) return tangent
def test_hermitian(self): """Test Hermitian variables. """ with self.assertRaises(Exception) as cm: v = Variable((4, 3), hermitian=True) self.assertEqual( str(cm.exception), "Invalid dimensions (4, 3). Must be a square matrix.") v = Variable((2, 2), hermitian=True) assert v.is_hermitian() # v = Variable((2,2), PSD=True) # assert v.is_symmetric() # v = Variable((2,2), NSD=True) # assert v.is_symmetric() v = Variable((2, 2), diag=True) assert v.is_hermitian() v = Variable((2, 2), hermitian=True) expr = v + v assert expr.is_hermitian() expr = -v assert expr.is_hermitian() expr = v.T assert expr.is_hermitian() expr = cp.real(v) assert expr.is_hermitian() expr = cp.imag(v) assert expr.is_hermitian() expr = cp.conj(v) assert expr.is_hermitian() expr = cp.promote(Variable(), (2, 2)) assert expr.is_hermitian()
def solve(self, solver='OLE'): ''' Method to solve the model Args: solver:'OLE' Ordinary Least Squares method 'Huber': Uses Huber smoother to down estimate the outliers. ''' W = cp.Variable((self.N, 1), complex=True) if solver.upper() == 'OLE': # Ordinary least squares _objective = cp.Minimize(cp.sum_squares(self.ALPHA @ W + self.A)) elif solver.upper( ) == 'HUBER': # TODO test Huber solver for robust optimization _real = cp.real(self.ALPHA @ W + self.A) _imag = cp.imag(self.ALPHA @ W + self.A) _objective = cp.Minimize( cp.sum_squares(cp.huber(cp.hstack([_real, _imag]), M=0))) elif solver.upper() == 'WLS': # TODO test weighted least squares _objective = cp.Minimize( cp.sum_squares(cp.diag(self.C) @ (self.ALPHA @ W + self.A))) else: raise tools.CustomError('Unrecognized Solver name') prob = cp.Problem(_objective) prob.solve() self.W = W.value return W.value
def test_psd(self) -> None: """Test Hermitian variables. """ X = Variable((2, 2), hermitian=True) prob = Problem(cp.Minimize(cp.imag(X[1, 0])), [X >> 0, X[0, 0] == -1]) prob.solve(solver="SCS") assert prob.status is cp.INFEASIBLE
def test_symmetric(self) -> None: """Test symmetric variables. """ with self.assertRaises(Exception) as cm: v = Variable((4, 3), symmetric=True) self.assertEqual( str(cm.exception), "Invalid dimensions (4, 3). Must be a square matrix.") v = Variable((2, 2), symmetric=True) assert v.is_symmetric() v = Variable((2, 2), PSD=True) assert v.is_symmetric() v = Variable((2, 2), NSD=True) assert v.is_symmetric() v = Variable((2, 2), diag=True) assert v.is_symmetric() assert self.a.is_symmetric() assert not self.A.is_symmetric() v = Variable((2, 2), symmetric=True) expr = v + v assert expr.is_symmetric() expr = -v assert expr.is_symmetric() expr = v.T assert expr.is_symmetric() expr = cp.real(v) assert expr.is_symmetric() expr = cp.imag(v) assert expr.is_symmetric() expr = cp.conj(v) assert expr.is_symmetric() expr = cp.promote(Variable(), (2, 2)) assert expr.is_symmetric()
def test_hermitian(self): """Test Hermitian variables. """ X = Variable((2, 2), hermitian=True) prob = Problem(cvx.Minimize(cvx.imag(X[1, 0])), [X[0, 0] == 2, X[1, 1] == 3, X[0, 1] == 1+1j]) prob.solve() self.assertItemsAlmostEqual(X.value, [2, 1-1j, 1+1j, 3])
def test_pnorm(self): """Test complex with pnorm. """ x = Variable((1, 2), complex=True) prob = Problem(cvx.Maximize(cvx.sum(cvx.imag(x) + cvx.real(x))), [cvx.norm1(x) <= 2]) result = prob.solve() self.assertAlmostEqual(result, 2*np.sqrt(2)) val = np.ones(2)*np.sqrt(2)/2 # self.assertItemsAlmostEqual(x.value, val + 1j*val) x = Variable((2, 2), complex=True) prob = Problem(cvx.Maximize(cvx.sum(cvx.imag(x) + cvx.real(x))), [cvx.pnorm(x, p=2) <= np.sqrt(8)]) result = prob.solve() self.assertAlmostEqual(result, 8) val = np.ones((2, 2)) self.assertItemsAlmostEqual(x.value, val + 1j*val)
def test_abs(self): """Test with absolute value. """ x = Variable(2, complex=True) prob = Problem(cvx.Maximize(cvx.sum(cvx.imag(x) + cvx.real(x))), [cvx.abs(x) <= 2]) result = prob.solve() self.assertAlmostEqual(result, 4*np.sqrt(2)) val = np.ones(2)*np.sqrt(2) self.assertItemsAlmostEqual(x.value, val + 1j*val)
def test_params(self): """Test with parameters. """ p = cvx.Parameter(imag=True, value=1j) x = Variable(2, complex=True) prob = Problem(cvx.Maximize(cvx.sum(cvx.imag(x) + cvx.real(x))), [cvx.abs(p*x) <= 2]) result = prob.solve() self.assertAlmostEqual(result, 4*np.sqrt(2)) val = np.ones(2)*np.sqrt(2) self.assertItemsAlmostEqual(x.value, val + 1j*val)
def test_imag(self): """Test imag. """ A = np.ones((2, 2)) expr = Constant(A) + 2j*Constant(A) expr = cvx.imag(expr) assert expr.is_real() assert not expr.is_complex() assert not expr.is_imag() self.assertItemsAlmostEqual(expr.value, 2*A)
def trace_constraint(mat_r: Variable, mat_i: Variable, trace: complex) -> List[Constraint]: """Return CVXPY trace constraints for a complex matrix. Args: mat_r: The CVXPY variable for the real part of the matrix. mat_i: The CVXPY variable for the complex part of the matrix. trace: The value for the trace constraint. Returns: A list of constraints on the real and imaginary parts. """ return [cvxpy.trace(mat_r) == cvxpy.real(trace), cvxpy.trace(mat_i) == cvxpy.imag(trace)]
def test_real(self): """Test real. """ A = np.ones((2, 2)) expr = Constant(A) + 1j*Constant(A) expr = cvx.real(expr) assert expr.is_real() assert not expr.is_complex() assert not expr.is_imag() self.assertItemsAlmostEqual(expr.value, A) x = Variable(complex=True) expr = cvx.imag(x) + cvx.real(x) assert expr.is_real()
def test_affine_atoms_canon(self): """Test canonicalization for affine atoms. """ # Scalar. x = Variable() expr = cvx.imag(x + 1j * x) prob = Problem(Minimize(expr), [x >= 0]) result = prob.solve() self.assertAlmostEqual(result, 0) self.assertAlmostEqual(x.value, 0) x = Variable(imag=True) expr = 1j * x prob = Problem(Minimize(expr), [cvx.imag(x) <= 1]) result = prob.solve() self.assertAlmostEqual(result, -1) self.assertAlmostEqual(x.value, 1j) x = Variable(2) expr = x / 1j prob = Problem(Minimize(expr[0] * 1j + expr[1] * 1j), [cvx.real(x + 1j) >= 1]) result = prob.solve() self.assertAlmostEqual(result, -np.inf) prob = Problem(Minimize(expr[0] * 1j + expr[1] * 1j), [cvx.real(x + 1j) <= 1]) result = prob.solve() self.assertAlmostEqual(result, -2) self.assertItemsAlmostEqual(x.value, [1, 1]) prob = Problem( Minimize(expr[0] * 1j + expr[1] * 1j), [cvx.real(x + 1j) >= 1, cvx.conj(x) <= 0]) result = prob.solve() self.assertAlmostEqual(result, np.inf) x = Variable((2, 2)) y = Variable((3, 2), complex=True) expr = cvx.vstack([x, y]) prob = Problem(Minimize(cvx.sum(cvx.imag(cvx.conj(expr)))), [x == 0, cvx.real(y) == 0, cvx.imag(y) <= 1]) result = prob.solve() self.assertAlmostEqual(result, -6) self.assertItemsAlmostEqual(y.value, 1j * np.ones((3, 2))) self.assertItemsAlmostEqual(x.value, np.zeros((2, 2))) x = Variable((2, 2)) y = Variable((3, 2), complex=True) expr = cvx.vstack([x, y]) prob = Problem(Minimize(cvx.sum(cvx.imag(expr.H))), [x == 0, cvx.real(y) == 0, cvx.imag(y) <= 1]) result = prob.solve() self.assertAlmostEqual(result, -6) self.assertItemsAlmostEqual(y.value, 1j * np.ones((3, 2))) self.assertItemsAlmostEqual(x.value, np.zeros((2, 2)))
def HCRB_sdp(rho, drho, solve="MOSEK", verbose_state=False): """ Calculate the HCRB of the statistical model define by rho and its derivatives. The derivation of this formulation of the HCRB and more details can be found in the paper: - [published](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.123.200503) - [arxiv](https://arxiv.org/abs/1906.05724) This current implementation currently assumes QuTip Qobjs for rho and drho. # TODO: tweak the code to take qobj or numpy arrays. Inputs: - rho: Qobj, parameter encoded state - drho: list[Qobj], dervatives of rho in the direction of the paramaters of interest - solve, SDP solver options: "MOSEK", "SCS" and "CVXOPT" [more details](https://www.cvxpy.org/tutorial/advanced/index.html) - verbose_state, if True then switches the SDP solver to verbose mode returns: The HRCB (float) for the statistical model defined by the state and its derivatives. """ # The SDP calculation is very sensitive to the input state being exactly hermitian. rho = (rho.dag() + rho) / 2 d = rho.dims[0][0] npar = len(drho) D, Vi = np.linalg.eigh(rho.full()) D = np.real(D) Vi = Vi[:, ::-1] D = D[::-1] Vi = qt.Qobj(Vi, dims=[[2] * n, [2] * n]) snonzero, rnk = rank(D) solver_options = {"MOSEK": cp.MOSEK, "CVXOPT": cp.CVXOPT, "SCS": cp.SCS} maskDiag = np.diag( np.ndarray.flatten( np.concatenate( (np.ones([rnk, 1], dtype=bool), np.zeros([d - rnk, 1], dtype=bool))))) maskRank = np.concatenate(( np.concatenate( ( np.triu(np.ones(rnk, dtype=bool), 1), np.zeros([rnk, d - rnk], dtype=bool), ), axis=1, ), np.zeros([d - rnk, d], dtype=bool), )) maskKern = np.concatenate(( np.concatenate( (np.zeros([rnk, rnk], dtype=bool), np.ones([rnk, d - rnk], dtype=bool)), axis=1, ), np.zeros([d - rnk, d], dtype=bool), )) fulldim = 2 * rnk * d - rnk**2 drhomat = np.zeros((fulldim, npar), dtype=np.complex_) for i in range(npar): drho[i] = (drho[i].dag() + drho[i]) / 2 eigdrho = (Vi.dag()) * drho[i] * Vi eigdrho = eigdrho.full() ak = eigdrho[maskKern] ak = ak.reshape((rnk, d - rnk)).transpose() ak = ak.reshape((rnk * (d - rnk))) row = np.concatenate(( eigdrho[maskDiag], np.real(eigdrho[maskRank]), np.imag(eigdrho[maskRank]), np.real(ak), np.imag(ak), )) drhomat[:, i] = row S = SmatRank(snonzero, d, rnk, fulldim) S = (S.transpose().conjugate() + S) / 2 R = Rmat(S) effdim = R.shape[0] idd = np.diag( np.ndarray.flatten( np.concatenate((np.ones((rnk)), 2 * np.ones((fulldim - rnk)))))) V = cp.Variable((npar, npar), PSD=True) X = cp.Variable((fulldim, npar)) A = cp.vstack([ cp.hstack([V, X.T @ R.conjugate().transpose()]), cp.hstack([R @ X, np.identity(effdim)]), ]) constraints = [ cp.vstack([ cp.hstack([cp.real(A), -cp.imag(A)]), cp.hstack([cp.imag(A), cp.real(A)]) ]) >> 0, X.T @ idd @ drhomat == np.identity(3), ] obj = cp.Minimize(cp.trace(V)) prob = cp.Problem(obj, constraints) prob.solve(solver=solver_options.get(solve, cp.SCS), verbose=verbose_state) out = prob.value return out
def getControllerParams(y, M): """ This function generates the response time optimal controller parameters for a permanent magnet motor, resulting in zero torque ripple under no measurement noise. It is assumed, for simplicity, that the control inputs are the currents instead of voltages. These currents, once known, can be used to determine the control voltage inputs. :param y: This is the torque vs theta function, which makes the permanent magnet motor model nonlinear. :param M: This is the order of the Fourier series approximation of the nonlinearity. :return: Returns the controller parameters, without the proportionality constant. """ plt.figure() plt.plot(np.linspace(-np.pi, np.pi, len(y)), y) plt.title('The plot of f(theta)') p = np.zeros(M) q = np.zeros(M) for k in range(M): p[k] = 2 * (np.cos( (k + 1) * np.linspace(-np.pi, np.pi, len(y))) @ y) / len(y) q[k] = 2 * (np.sin( (k + 1) * np.linspace(-np.pi, np.pi, len(y))) @ y) / len(y) s = np.zeros(len(y)) x = np.linspace(-np.pi, np.pi, len(y)) for k in range(len(y)): s[k] = (np.cos(np.linspace(1, M, M) * x[k]) @ p + np.sin(np.linspace(1, M, M) * x[k]) @ q) plt.figure() plt.plot(s) plt.plot(y) plt.title('Comparison with the Fourier truncation') Z = np.zeros([4 * M + 1, 2 * M]) #cos and cos for k in range(1, M + 1): for l in range(1, M + 1): if (l == k): Z[0, l - 1] = Z[0, l - 1] + p[k - 1] / 2 Z[2 * (k + l) - 1, l - 1] = Z[2 * (k + l) - 1, l - 1] + p[k - 1] / 2 else: Z[2 * (k + l) - 1, l - 1] = Z[2 * (k + l) - 1, l - 1] + p[k - 1] / 2 Z[2 * np.abs(k - l) - 1, l - 1] = Z[2 * abs(k - l) - 1, l - 1] + p[k - 1] / 2 #cos and sin for k in range(1, M + 1): for l in range(1, M + 1): if (l == k): Z[2 * (k + l) + 1 - 1, l + M - 1] = Z[2 * (k + l) + 1 - 1, l + M - 1] + p[k - 1] / 2 else: Z[2 * (k + l) + 1 - 1, l + M - 1] = Z[2 * (k + l) + 1 - 1, l + M - 1] + p[k - 1] / 2 Z[2 * abs(k - l) + 1 - 1, l + M - 1] = Z[2 * abs(k - l) + 1 - 1, l + M - 1] + np.sign(l - k) * p[k - 1] / 2 # sin and cos for k in range(1, M + 1): for l in range(1, M + 1): if (l == k): Z[2 * (k + l) + 1 - 1, l - 1] = Z[2 * (k + l) + 1 - 1, l - 1] + q[k - 1] / 2 else: Z[2 * (k + l) + 1 - 1, l - 1] = Z[2 * (k + l) + 1 - 1, l - 1] + q[k - 1] / 2 Z[2 * np.abs(k - l) + 1 - 1, l - 1] = Z[2 * np.abs(k - l) + 1 - 1, l - 1] + np.sign(k - l) * q[k - 1] / 2 # sin and sin for k in range(1, M + 1): for l in range(1, M + 1): if (l == k): Z[1 - 1, l + M - 1] = Z[1 - 1, l + M - 1] + q[k - 1] / 2 Z[2 * (k + l) - 1, l + M - 1] = Z[2 * (k + l) - 1, l + M - 1] - q[k - 1] / 2 else: Z[2 * (k + l) - 1, l + M - 1] = Z[2 * (k + l) - 1, l + M - 1] - q[k - 1] / 2 Z[2 * abs(k - l) - 1, l + M - 1] = Z[2 * abs(k - l) - 1, l + M - 1] + q[k - 1] / 2 A = np.zeros([4 * M, 4 * M]) for i in range(1, 2 * M + 1): A[2 * (i - 1), 2 * (i - 1)] = np.cos(2 * np.pi * i / 3) A[2 * (i - 1), 2 * i - 1] = np.sin(2 * np.pi * i / 3) A[2 * i - 1, 2 * (i - 1)] = -np.sin(2 * np.pi * i / 3) A[2 * i - 1, 2 * i - 1] = np.cos(2 * np.pi * i / 3) A = np.vstack((np.hstack( (1, np.zeros(4 * M))), np.hstack((np.zeros([4 * M, 1]), A)))) B = np.zeros([4 * M, 4 * M]) for i in range(1, 2 * M + 1): B[2 * (i - 1), 2 * (i - 1)] = np.cos(4 * np.pi * i / 3) B[2 * (i - 1), 2 * i - 1] = np.sin(4 * np.pi * i / 3) B[2 * i - 1, 2 * (i - 1)] = -np.sin(4 * np.pi * i / 3) B[2 * i - 1, 2 * i - 1] = np.cos(4 * np.pi * i / 3) B = np.vstack((np.hstack( (1, np.zeros(4 * M))), np.hstack((np.zeros([4 * M, 1]), B)))) G = np.hstack((Z, A @ Z, B @ Z)) As1 = np.vstack((np.hstack( (np.zeros(M - 1), 0)), np.hstack((np.eye(M - 1), np.zeros([M - 1, 1]))))) Bs1 = np.vstack((1, np.zeros([M - 1, 1]))) As2 = np.vstack((np.hstack( (np.zeros(M - 1), 0)), np.hstack((np.eye(M - 1), np.zeros([M - 1, 1]))))) Bs2 = np.vstack((1, np.zeros([M - 1, 1]))) As3 = np.vstack((np.hstack( (np.zeros(M - 1), 0)), np.hstack((np.eye(M - 1), np.zeros([M - 1, 1]))))) Bs3 = np.vstack((1, np.zeros([M - 1, 1]))) p1 = cvx.Variable((1, M)) q1 = cvx.Variable((1, M)) p2 = cvx.Variable((1, M)) q2 = cvx.Variable((1, M)) p3 = cvx.Variable((1, M)) q3 = cvx.Variable((1, M)) Q1l = cvx.Variable((M, M), hermitian=True) Q2l = cvx.Variable((M, M), hermitian=True) Q3l = cvx.Variable((M, M), hermitian=True) Q1u = cvx.Variable((M, M), hermitian=True) Q2u = cvx.Variable((M, M), hermitian=True) Q3u = cvx.Variable((M, M), hermitian=True) Ds1u = cvx.Variable() Ds2u = cvx.Variable() Ds3u = cvx.Variable() Ds1l = cvx.Variable() Ds2l = cvx.Variable() Ds3l = cvx.Variable() z = cvx.Variable() Cs1u = cvx.Variable((1, M), complex=True) Cs2u = cvx.Variable((1, M), complex=True) Cs3u = cvx.Variable((1, M), complex=True) Cs1l = cvx.Variable((1, M), complex=True) Cs2l = cvx.Variable((1, M), complex=True) Cs3l = cvx.Variable((1, M), complex=True) r1u = cvx.Variable((1, M), complex=True) r2u = cvx.Variable((1, M), complex=True) r3u = cvx.Variable((1, M), complex=True) r1l = cvx.Variable((1, M), complex=True) r2l = cvx.Variable((1, M), complex=True) r3l = cvx.Variable((1, M), complex=True) constraints = [] constraints = constraints + [ G @ (cvx.hstack((p1, q1, p2, q2, p3, q3)).T) == np.vstack( (1, np.zeros([4 * M, 1]))) ] constraints = constraints + [ cvx.real(r1u) == -p1 / 2, cvx.imag(r1u) == q1 / 2 ] constraints = constraints + [ cvx.real(r2u) == -p2 / 2, cvx.imag(r2u) == q2 / 2 ] constraints = constraints + [ cvx.real(r3u) == -p3 / 2, cvx.imag(r3u) == q3 / 2 ] constraints = constraints + [ cvx.real(r1l) == p1 / 2, cvx.imag(r1l) == -q1 / 2 ] constraints = constraints + [ cvx.real(r2l) == p2 / 2, cvx.imag(r2l) == -q2 / 2 ] constraints = constraints + [ cvx.real(r3l) == p3 / 2, cvx.imag(r3l) == -q3 / 2 ] constraints = constraints + [Cs1u == r1u] constraints = constraints + [Cs2u == r2u] constraints = constraints + [Cs3u == r3u] constraints = constraints + [Cs1l == r1l] constraints = constraints + [Cs2l == r2l] constraints = constraints + [Cs3l == r3l] constraints = constraints + [Ds1u == z / 2] constraints = constraints + [Ds2u == z / 2] constraints = constraints + [Ds3u == z / 2] constraints = constraints + [Ds1l == z / 2] constraints = constraints + [Ds2l == z / 2] constraints = constraints + [Ds3l == z / 2] constraints = constraints + [ cvx.vstack( (cvx.hstack((Q1u - (As1.T) @ Q1u @ As1, -(As1.T) @ Q1u @ Bs1)), cvx.hstack((-cvx.conj( (As1.T) @ Q1u @ Bs1).T, -(Bs1.T) @ Q1u @ Bs1)))) + cvx.vstack( (cvx.hstack((np.zeros([M, M]), -cvx.conj(Cs1u).T)), cvx.hstack( (-Cs1u, cvx.reshape(Ds1u + Ds1u, [1, 1]))))) >> 0 ] constraints = constraints + [ cvx.vstack( (cvx.hstack((Q2u - (As2.T) @ Q2u @ As2, -(As2.T) @ Q2u @ Bs2)), cvx.hstack((-cvx.conj( (As2.T) @ Q2u @ Bs2).T, -(Bs2.T) @ Q2u @ Bs2)))) + cvx.vstack( (cvx.hstack((np.zeros([M, M]), -cvx.conj(Cs2u).T)), cvx.hstack( (-Cs2u, cvx.reshape(Ds2u + Ds2u, [1, 1]))))) >> 0 ] constraints = constraints + [ cvx.vstack( (cvx.hstack((Q3u - (As3.T) @ Q3u @ As3, -(As3.T) @ Q3u @ Bs3)), cvx.hstack((-cvx.conj( (As3.T) @ Q3u @ Bs3).T, -(Bs3.T) @ Q3u @ Bs3)))) + cvx.vstack( (cvx.hstack((np.zeros([M, M]), -cvx.conj(Cs3u).T)), cvx.hstack( (-Cs3u, cvx.reshape(Ds3u + Ds3u, [1, 1]))))) >> 0 ] constraints = constraints + [ cvx.vstack( (cvx.hstack((Q1l - (As1.T) @ Q1l @ As1, -(As1.T) @ Q1l @ Bs1)), cvx.hstack((-cvx.conj( (As1.T) @ Q1l @ Bs1).T, -(Bs1.T) @ Q1l @ Bs1)))) + cvx.vstack( (cvx.hstack((np.zeros([M, M]), -cvx.conj(Cs1l).T)), cvx.hstack( (-Cs1l, cvx.reshape(Ds1l + Ds1l, [1, 1]))))) >> 0 ] constraints = constraints + [ cvx.vstack( (cvx.hstack((Q2l - (As2.T) @ Q2l @ As2, -(As2.T) @ Q2l @ Bs2)), cvx.hstack((-cvx.conj( (As2.T) @ Q2l @ Bs2).T, -(Bs2.T) @ Q2l @ Bs2)))) + cvx.vstack( (cvx.hstack((np.zeros([M, M]), -cvx.conj(Cs2l).T)), cvx.hstack( (-Cs2l, cvx.reshape(Ds2l + Ds2l, [1, 1]))))) >> 0 ] constraints = constraints + [ cvx.vstack( (cvx.hstack((Q3l - (As3.T) @ Q3l @ As3, -(As3.T) @ Q3l @ Bs3)), cvx.hstack((-cvx.conj( (As3.T) @ Q3l @ Bs3).T, -(Bs3.T) @ Q3l @ Bs3)))) + cvx.vstack( (cvx.hstack((np.zeros([M, M]), -cvx.conj(Cs3l).T)), cvx.hstack( (-Cs3l, cvx.reshape(Ds3l + Ds3l, [1, 1]))))) >> 0 ] prob = cvx.Problem(cvx.Minimize(z), constraints) prob.solve(solver=cvx.SCS, verbose=True) return p, q, z.value, p1.value, p2.value, p3.value, q1.value, q2.value, q3.value
def naghol_sdp(phi, dphi, d, solve="MOSEK", verbose_state=False): """ args: -phi : qutip quantum object, pure normalised state, of size (d, 1) -dphi: qutip quantum object, vector of derivatives, of size (d, 1) x npar, where npar is the number of parameters -d : integer , dimension of Hilbert space default args: -solver : string, string of solver, default is MOSEK, can also be CVXOPT, SCS. More details: https://www.cvxpy.org/tutorial/advanced/index.html -verbose_state : bool , option if you want the cvxpy solver to turn maximal information or not. verbose_state = true prints max info to terminal returns: float, Holevo-Cramer-Rao bound of the statistical model defined by the encoded state and derivatives """ # dictionary of SDP solvers solver_options = {"MOSEK": cp.MOSEK, "CVXOPT": cp.CVXOPT, "SCS": cp.SCS} # define the number of parameters npar = len(dphi) # here we define the matrix of horizontal lifts-Lmat # The horizontal lift is defined as: 2(|\partial_i \phi> − <\phi| \partial_i \phi>|\phi> ) psidpsi = phi.dag() * dphi pardphi = phi * psidpsi Lmat = 2 * (dphi - pardphi) # convert quantum objects to numpy arrays # n.b. this should be sped up by just calling phi.full(), will do this and test asap psi = np.zeros(d, dtype=complex) for i in range(d): psi[i] = phi[i][0][0] # same here as above with horizontal lifts Lmatt = np.zeros((d, npar), dtype=complex) for i in range(d): for j in range(npar): Lmatt[i, j] = Lmat[j][i] # set up SDP as defined in the readme/paper # set up variables 'variance matrix' V and 'derivative variable matrices' X V = cp.Variable((npar, npar), PSD=True) X = cp.Variable((d, npar), complex=True) # set up SD-matrix to optimise A = cp.vstack([cp.hstack([V, X.H]), cp.hstack([X, np.identity(d)])]) # constraints need to be split because cvxpy will only accept a real valued SD-matrix constraints = [ cp.vstack([ cp.hstack([cp.real(A), -cp.imag(A)]), cp.hstack([cp.imag(A), cp.real(A)]) ]) >> 0, cp.real(X.H @ Lmatt) == np.identity(3), ((psi.transpose()).conjugate() @ X) == 0, ] # define objective function obj = cp.Minimize(cp.trace(V)) # define problem prob = cp.Problem(obj, constraints) # solve problem prob.solve(solver=solver_options[solve], verbose=verbose_state) # extract and return value out = prob.value return out