11.9 Nearest Correlation Matrix Problem¶
A correlation matrix is a symmetric positive definite matrix with unit diagonal. This term has origins in statistics, since the matrix whose entries are the correlation coefficients of a sequence of random variables has all these properties.
In this section we study variants of the problem of approximating a given symmetric matrix
find the correlation matrix
nearest to in the Frobenius norm,find an approximation of the form
where is a diagonal matrix with positive diagonal and is a positive semidefinite matrix of low rank, using the combination of Frobenius and nuclear norm.
Both problems are related to portfolio optimization, where one can often have a matrix
11.9.1 Nearest correlation with the Frobenius norm¶
The Frobenius norm of a real matrix
and with respect to this norm our optimization problem can be expressed simply as:
We can exploit the symmetry of
Note that
Expression::t vec(Expression::t e)
{
int N = (*e->getShape())[0];
int dim = N * (N + 1) / 2;
auto msubi = new_array_ptr<int, 1>(dim);
auto msubj = new_array_ptr<int, 1>(dim);
auto mcof = new_array_ptr<double, 1>(dim);
for (int i = 0, k = 0; i < N; ++i)
for (int j = 0; j < i + 1; ++j, ++k)
{
(*msubi)[k] = k;
(*msubj)[k] = i * N + j;
(*mcof) [k] = (i == j) ? 1.0 : std::sqrt(2.0);
}
Matrix::t S = Matrix::sparse(N * (N + 1) / 2, N * N, msubi, msubj, mcof);
return Expr::mul(S, Expr::reshape(e, N * N));
}
That leads to an optimization problem with both conic quadratic and semidefinite constraints:
Code example
void nearestcorr( std::shared_ptr<ndarray<double, 2>> A)
{
int N = A->size(0);
// Create a model
Model::t M = new Model("NearestCorrelation"); auto _M = finally([&]() { M->dispose(); });
// Setting up the variables
Variable::t X = M->variable("X", Domain::inPSDCone(N));
Variable::t t = M->variable("t", 1, Domain::unbounded());
// (t, vec (A-X)) \in Q
M->constraint( Expr::vstack(t, vec(Expr::sub(A, X))), Domain::inQCone() );
// diag(X) = e
M->constraint(X->diag(), Domain::equalsTo(1.0));
// Objective: Minimize t
M->objective(ObjectiveSense::Minimize, t);
// Solve the problem
M->solve();
// Get the solution values
std::cout << "X = \n"; print_mat(std::cout, X->level());
std::cout << "t = " << *(t->level()->begin()) << std::endl;
}
We use the following input
int N = 5;
auto A = new_array_ptr<double, 2>(
{ { 0.0, 0.5, -0.1, -0.2, 0.5},
{ 0.5, 1.25, -0.05, -0.1, 0.25},
{ -0.1, -0.05, 0.51, 0.02, -0.05},
{ -0.2, -0.1, 0.02, 0.54, -0.1},
{ 0.5, 0.25, -0.05, -0.1, 1.25}
});
The expected output is the following (small differences may apply):
X =
[[ 1. 0.50001941 -0.09999994 -0.20000084 0.50001941]
[ 0.50001941 1. -0.04999551 -0.09999154 0.24999101]
[-0.09999994 -0.04999551 1. 0.01999746 -0.04999551]
[-0.20000084 -0.09999154 0.01999746 1. -0.09999154]
[ 0.50001941 0.24999101 -0.04999551 -0.09999154 1. ]]
11.9.2 Nearest Correlation with Nuclear-norm Penalty¶
Next, we consider the approximation of
The combination of these constraints leads to a problem:
where the parameter
Exploit the mapping
Code example
void nearestcorr_nn(
std::shared_ptr<ndarray<double, 2>> A,
const std::vector<double> & gammas,
std::vector<double> & res,
std::vector<double> & rank)
{
int N = A->size(0);
Model::t M = new Model("NucNorm"); auto M_ = monty::finally([&]() { M->dispose(); });
// Setup variables
Variable::t t = M->variable("t", 1, Domain::unbounded());
Variable::t X = M->variable("X", Domain::inPSDCone(N));
Variable::t w = M->variable("w", N, Domain::greaterThan(0.0));
// (t, vec (X + diag(w) - A)) in Q
Expression::t D = Expr::mulElm( Matrix::eye(N), Var::repeat(w, N, 1) );
M->constraint( Expr::vstack( t, vec(Expr::sub(Expr::add(X, D), A)) ), Domain::inQCone() );
// Trace(X)
auto TrX = Expr::sum(X->diag());
for (int k = 0; k < gammas.size(); ++k)
{
// Objective: Minimize t + gamma*Tr(X)
M->objective(ObjectiveSense::Minimize, Expr::add(t, Expr::mul(gammas[k], TrX )));
M->solve();
// Find the eigenvalues of X and approximate its rank
auto d = new_array_ptr<double, 1>(N);
mosek::LinAlg::syeig(MSK_UPLO_LO, N, X->level(), d);
int rnk = 0; for (int i = 0; i < N; ++i) if ((*d)[i] > 1e-6) ++rnk;
res[k] = (*(t->level()))[0];
rank[k] = rnk;
}
}
We feed MOSEK with the same input as in Sec. 11.9.1 (Nearest correlation with the Frobenius norm). The problem is solved for a range of values
--- Nearest Correlation with Nuclear Norm---
gamma=0.000000, res=3.076163e-01, rank=4
gamma=0.100000, res=4.251692e-01, rank=2
gamma=0.200000, res=5.112082e-01, rank=1
gamma=0.300000, res=5.298432e-01, rank=1
gamma=0.400000, res=5.592686e-01, rank=1
gamma=0.500000, res=6.045702e-01, rank=1
gamma=0.600000, res=6.764402e-01, rank=1
gamma=0.700000, res=8.009913e-01, rank=1
gamma=0.800000, res=1.062385e+00, rank=1
gamma=0.900000, res=1.129513e+00, rank=0
gamma=1.000000, res=1.129513e+00, rank=0