#include <iostream>
#include <algorithm>
#include <cmath>
#include <limits>
#include <sstream>
#include <stdexcept>
#include "QuadProg++.hh"
namespace quadprogpp {
void compute_d(Vector<double>& d, const Matrix<double>& J, const Vector<double>& np);
void update_z(Vector<double>& z, const Matrix<double>& J, const Vector<double>& d, int iq);
void update_r(const Matrix<double>& R, Vector<double>& r, const Vector<double>& d, int iq);
bool add_constraint(Matrix<double>& R, Matrix<double>& J, Vector<double>& d, unsigned int& iq, double& rnorm);
void delete_constraint(Matrix<double>& R, Matrix<double>& J, Vector<int>& A, Vector<double>& u, unsigned int n, int p, unsigned int& iq, int l);
void cholesky_decomposition(Matrix<double>& A);
void cholesky_solve(const Matrix<double>& L, Vector<double>& x, const Vector<double>& b);
void forward_elimination(const Matrix<double>& L, Vector<double>& y, const Vector<double>& b);
void backward_elimination(const Matrix<double>& U, Vector<double>& x, const Vector<double>& y);
double scalar_product(const Vector<double>& x, const Vector<double>& y);
double distance(double a, double b);
void print_matrix(const char* name, const Matrix<double>& A, int n = -1, int m = -1);
template<typename T>
void print_vector(const char* name, const Vector<T>& v, int n = -1);
double solve_quadprog(Matrix<double>& G, Vector<double>& g0,
const Matrix<double>& CE, const Vector<double>& ce0,
const Matrix<double>& CI, const Vector<double>& ci0,
Vector<double>& x)
{
std::ostringstream msg;
unsigned int n = G.ncols(), p = CE.ncols(), m = CI.ncols();
if (G.nrows() != n)
{
msg << "The matrix G is not a squared matrix (" << G.nrows() << " x " << G.ncols() << ")";
throw std::logic_error(msg.str());
}
if (CE.nrows() != n)
{
msg << "The matrix CE is incompatible (incorrect number of rows " << CE.nrows() << " , expecting " << n << ")";
throw std::logic_error(msg.str());
}
if (ce0.size() != p)
{
msg << "The vector ce0 is incompatible (incorrect dimension " << ce0.size() << ", expecting " << p << ")";
throw std::logic_error(msg.str());
}
if (CI.nrows() != n)
{
msg << "The matrix CI is incompatible (incorrect number of rows " << CI.nrows() << " , expecting " << n << ")";
throw std::logic_error(msg.str());
}
if (ci0.size() != m)
{
msg << "The vector ci0 is incompatible (incorrect dimension " << ci0.size() << ", expecting " << m << ")";
throw std::logic_error(msg.str());
}
x.resize(n);
register unsigned int i, j, k, l;
int ip; Matrix<double> R(n, n), J(n, n);
Vector<double> s(m + p), z(n), r(m + p), d(n), np(n), u(m + p), x_old(n), u_old(m + p);
double f_value, psi, c1, c2, sum, ss, R_norm;
double inf;
if (std::numeric_limits<double>::has_infinity)
inf = std::numeric_limits<double>::infinity();
else
inf = 1.0E300;
double t, t1, t2;
Vector<int> A(m + p), A_old(m + p), iai(m + p);
unsigned int iq, iter = 0;
Vector<bool> iaexcl(m + p);
#ifdef TRACE_SOLVER
std::cout << std::endl << "Starting solve_quadprog" << std::endl;
print_matrix("G", G);
print_vector("g0", g0);
print_matrix("CE", CE);
print_vector("ce0", ce0);
print_matrix("CI", CI);
print_vector("ci0", ci0);
#endif
c1 = 0.0;
for (i = 0; i < n; i++)
{
c1 += G[i][i];
}
cholesky_decomposition(G);
#ifdef TRACE_SOLVER
print_matrix("G", G);
#endif
for (i = 0; i < n; i++)
{
d[i] = 0.0;
for (j = 0; j < n; j++)
R[i][j] = 0.0;
}
R_norm = 1.0;
c2 = 0.0;
for (i = 0; i < n; i++)
{
d[i] = 1.0;
forward_elimination(G, z, d);
for (j = 0; j < n; j++)
J[i][j] = z[j];
c2 += z[i];
d[i] = 0.0;
}
#ifdef TRACE_SOLVER
print_matrix("J", J);
#endif
cholesky_solve(G, x, g0);
for (i = 0; i < n; i++)
x[i] = -x[i];
f_value = 0.5 * scalar_product(g0, x);
#ifdef TRACE_SOLVER
std::cout << "Unconstrained solution: " << f_value << std::endl;
print_vector("x", x);
#endif
iq = 0;
for (i = 0; i < p; i++)
{
for (j = 0; j < n; j++)
np[j] = CE[j][i];
compute_d(d, J, np);
update_z(z, J, d, iq);
update_r(R, r, d, iq);
#ifdef TRACE_SOLVER
print_matrix("R", R, n, iq);
print_vector("z", z);
print_vector("r", r, iq);
print_vector("d", d);
#endif
t2 = 0.0;
if (fabs(scalar_product(z, z)) > std::numeric_limits<double>::epsilon()) t2 = (-scalar_product(np, x) - ce0[i]) / scalar_product(z, np);
for (k = 0; k < n; k++)
x[k] += t2 * z[k];
u[iq] = t2;
for (k = 0; k < iq; k++)
u[k] -= t2 * r[k];
f_value += 0.5 * (t2 * t2) * scalar_product(z, np);
A[i] = -i - 1;
if (!add_constraint(R, J, d, iq, R_norm))
{
throw std::runtime_error("Constraints are linearly dependent");
return f_value;
}
}
for (i = 0; i < m; i++)
iai[i] = i;
l1: iter++;
#ifdef TRACE_SOLVER
print_vector("x", x);
#endif
for (i = p; i < iq; i++)
{
ip = A[i];
iai[ip] = -1;
}
ss = 0.0;
psi = 0.0;
ip = 0;
for (i = 0; i < m; i++)
{
iaexcl[i] = true;
sum = 0.0;
for (j = 0; j < n; j++)
sum += CI[j][i] * x[j];
sum += ci0[i];
s[i] = sum;
psi += std::min(0.0, sum);
}
#ifdef TRACE_SOLVER
print_vector("s", s, m);
#endif
if (fabs(psi) <= m * std::numeric_limits<double>::epsilon() * c1 * c2* 100.0)
{
return f_value;
}
for (i = 0; i < iq; i++)
{
u_old[i] = u[i];
A_old[i] = A[i];
}
for (i = 0; i < n; i++)
x_old[i] = x[i];
l2:
for (i = 0; i < m; i++)
{
if (s[i] < ss && iai[i] != -1 && iaexcl[i])
{
ss = s[i];
ip = i;
}
}
if (ss >= 0.0)
{
return f_value;
}
for (i = 0; i < n; i++)
np[i] = CI[i][ip];
u[iq] = 0.0;
A[iq] = ip;
#ifdef TRACE_SOLVER
std::cout << "Trying with constraint " << ip << std::endl;
print_vector("np", np);
#endif
l2a:
compute_d(d, J, np);
update_z(z, J, d, iq);
update_r(R, r, d, iq);
#ifdef TRACE_SOLVER
std::cout << "Step direction z" << std::endl;
print_vector("z", z);
print_vector("r", r, iq + 1);
print_vector("u", u, iq + 1);
print_vector("d", d);
print_vector("A", A, iq + 1);
#endif
l = 0;
t1 = inf;
for (k = p; k < iq; k++)
{
if (r[k] > 0.0)
{
if (u[k] / r[k] < t1)
{
t1 = u[k] / r[k];
l = A[k];
}
}
}
if (fabs(scalar_product(z, z)) > std::numeric_limits<double>::epsilon()) {
t2 = -s[ip] / scalar_product(z, np);
if (t2 < 0) t2 = inf;
}
else
t2 = inf;
t = std::min(t1, t2);
#ifdef TRACE_SOLVER
std::cout << "Step sizes: " << t << " (t1 = " << t1 << ", t2 = " << t2 << ") ";
#endif
if (t >= inf)
{
return inf;
}
if (t2 >= inf)
{
for (k = 0; k < iq; k++)
u[k] -= t * r[k];
u[iq] += t;
iai[l] = l;
delete_constraint(R, J, A, u, n, p, iq, l);
#ifdef TRACE_SOLVER
std::cout << " in dual space: "
<< f_value << std::endl;
print_vector("x", x);
print_vector("z", z);
print_vector("A", A, iq + 1);
#endif
goto l2a;
}
for (k = 0; k < n; k++)
x[k] += t * z[k];
f_value += t * scalar_product(z, np) * (0.5 * t + u[iq]);
for (k = 0; k < iq; k++)
u[k] -= t * r[k];
u[iq] += t;
#ifdef TRACE_SOLVER
std::cout << " in both spaces: "
<< f_value << std::endl;
print_vector("x", x);
print_vector("u", u, iq + 1);
print_vector("r", r, iq + 1);
print_vector("A", A, iq + 1);
#endif
if (fabs(t - t2) < std::numeric_limits<double>::epsilon())
{
#ifdef TRACE_SOLVER
std::cout << "Full step has taken " << t << std::endl;
print_vector("x", x);
#endif
if (!add_constraint(R, J, d, iq, R_norm))
{
iaexcl[ip] = false;
delete_constraint(R, J, A, u, n, p, iq, ip);
#ifdef TRACE_SOLVER
print_matrix("R", R);
print_vector("A", A, iq);
print_vector("iai", iai);
#endif
for (i = 0; i < m; i++)
iai[i] = i;
for (i = p; i < iq; i++)
{
A[i] = A_old[i];
u[i] = u_old[i];
iai[A[i]] = -1;
}
for (i = 0; i < n; i++)
x[i] = x_old[i];
goto l2;
}
else
iai[ip] = -1;
#ifdef TRACE_SOLVER
print_matrix("R", R);
print_vector("A", A, iq);
print_vector("iai", iai);
#endif
goto l1;
}
#ifdef TRACE_SOLVER
std::cout << "Partial step has taken " << t << std::endl;
print_vector("x", x);
#endif
iai[l] = l;
delete_constraint(R, J, A, u, n, p, iq, l);
#ifdef TRACE_SOLVER
print_matrix("R", R);
print_vector("A", A, iq);
#endif
sum = 0.0;
for (k = 0; k < n; k++)
sum += CI[k][ip] * x[k];
s[ip] = sum + ci0[ip];
#ifdef TRACE_SOLVER
print_vector("s", s, m);
#endif
goto l2a;
}
inline void compute_d(Vector<double>& d, const Matrix<double>& J, const Vector<double>& np)
{
register int i, j, n = d.size();
register double sum;
for (i = 0; i < n; i++)
{
sum = 0.0;
for (j = 0; j < n; j++)
sum += J[j][i] * np[j];
d[i] = sum;
}
}
inline void update_z(Vector<double>& z, const Matrix<double>& J, const Vector<double>& d, int iq)
{
register int i, j, n = z.size();
for (i = 0; i < n; i++)
{
z[i] = 0.0;
for (j = iq; j < n; j++)
z[i] += J[i][j] * d[j];
}
}
inline void update_r(const Matrix<double>& R, Vector<double>& r, const Vector<double>& d, int iq)
{
register int i, j;
register double sum;
for (i = iq - 1; i >= 0; i--)
{
sum = 0.0;
for (j = i + 1; j < iq; j++)
sum += R[i][j] * r[j];
r[i] = (d[i] - sum) / R[i][i];
}
}
bool add_constraint(Matrix<double>& R, Matrix<double>& J, Vector<double>& d, unsigned int& iq, double& R_norm)
{
unsigned int n = d.size();
#ifdef TRACE_SOLVER
std::cout << "Add constraint " << iq << '/';
#endif
register unsigned int i, j, k;
double cc, ss, h, t1, t2, xny;
for (j = n - 1; j >= iq + 1; j--)
{
cc = d[j - 1];
ss = d[j];
h = distance(cc, ss);
if (fabs(h) < std::numeric_limits<double>::epsilon()) continue;
d[j] = 0.0;
ss = ss / h;
cc = cc / h;
if (cc < 0.0)
{
cc = -cc;
ss = -ss;
d[j - 1] = -h;
}
else
d[j - 1] = h;
xny = ss / (1.0 + cc);
for (k = 0; k < n; k++)
{
t1 = J[k][j - 1];
t2 = J[k][j];
J[k][j - 1] = t1 * cc + t2 * ss;
J[k][j] = xny * (t1 + J[k][j - 1]) - t2;
}
}
iq++;
for (i = 0; i < iq; i++)
R[i][iq - 1] = d[i];
#ifdef TRACE_SOLVER
std::cout << iq << std::endl;
print_matrix("R", R, iq, iq);
print_matrix("J", J);
print_vector("d", d, iq);
#endif
if (fabs(d[iq - 1]) <= std::numeric_limits<double>::epsilon() * R_norm)
{
return false;
}
R_norm = std::max<double>(R_norm, fabs(d[iq - 1]));
return true;
}
void delete_constraint(Matrix<double>& R, Matrix<double>& J, Vector<int>& A, Vector<double>& u, unsigned int n, int p, unsigned int& iq, int l)
{
#ifdef TRACE_SOLVER
std::cout << "Delete constraint " << l << ' ' << iq;
#endif
register unsigned int i, j, k, qq = 0; double cc, ss, h, xny, t1, t2;
bool found = false;
for (i = p; i < iq; i++)
if (A[i] == l)
{
qq = i;
found = true;
break;
}
if(!found)
{
std::ostringstream os;
os << "Attempt to delete non existing constraint, constraint: " << l;
throw std::invalid_argument(os.str());
}
for (i = qq; i < iq - 1; i++)
{
A[i] = A[i + 1];
u[i] = u[i + 1];
for (j = 0; j < n; j++)
R[j][i] = R[j][i + 1];
}
A[iq - 1] = A[iq];
u[iq - 1] = u[iq];
A[iq] = 0;
u[iq] = 0.0;
for (j = 0; j < iq; j++)
R[j][iq - 1] = 0.0;
iq--;
#ifdef TRACE_SOLVER
std::cout << '/' << iq << std::endl;
#endif
if (iq == 0)
return;
for (j = qq; j < iq; j++)
{
cc = R[j][j];
ss = R[j + 1][j];
h = distance(cc, ss);
if (fabs(h) < std::numeric_limits<double>::epsilon()) continue;
cc = cc / h;
ss = ss / h;
R[j + 1][j] = 0.0;
if (cc < 0.0)
{
R[j][j] = -h;
cc = -cc;
ss = -ss;
}
else
R[j][j] = h;
xny = ss / (1.0 + cc);
for (k = j + 1; k < iq; k++)
{
t1 = R[j][k];
t2 = R[j + 1][k];
R[j][k] = t1 * cc + t2 * ss;
R[j + 1][k] = xny * (t1 + R[j][k]) - t2;
}
for (k = 0; k < n; k++)
{
t1 = J[k][j];
t2 = J[k][j + 1];
J[k][j] = t1 * cc + t2 * ss;
J[k][j + 1] = xny * (J[k][j] + t1) - t2;
}
}
}
inline double distance(double a, double b)
{
register double a1, b1, t;
a1 = fabs(a);
b1 = fabs(b);
if (a1 > b1)
{
t = (b1 / a1);
return a1 * sqrt(1.0 + t * t);
}
else
if (b1 > a1)
{
t = (a1 / b1);
return b1 * sqrt(1.0 + t * t);
}
return a1 * sqrt(2.0);
}
inline double scalar_product(const Vector<double>& x, const Vector<double>& y)
{
register int i, n = x.size();
register double sum;
sum = 0.0;
for (i = 0; i < n; i++)
sum += x[i] * y[i];
return sum;
}
void cholesky_decomposition(Matrix<double>& A)
{
register int i, j, k, n = A.nrows();
register double sum;
for (i = 0; i < n; i++)
{
for (j = i; j < n; j++)
{
sum = A[i][j];
for (k = i - 1; k >= 0; k--)
sum -= A[i][k]*A[j][k];
if (i == j)
{
if (sum <= 0.0)
{
std::ostringstream os;
print_matrix("A", A);
os << "Error in cholesky decomposition, sum: " << sum;
throw std::logic_error(os.str());
exit(-1);
}
A[i][i] = sqrt(sum);
}
else
A[j][i] = sum / A[i][i];
}
for (k = i + 1; k < n; k++)
A[i][k] = A[k][i];
}
}
void cholesky_solve(const Matrix<double>& L, Vector<double>& x, const Vector<double>& b)
{
int n = L.nrows();
Vector<double> y(n);
forward_elimination(L, y, b);
backward_elimination(L, x, y);
}
inline void forward_elimination(const Matrix<double>& L, Vector<double>& y, const Vector<double>& b)
{
register int i, j, n = L.nrows();
y[0] = b[0] / L[0][0];
for (i = 1; i < n; i++)
{
y[i] = b[i];
for (j = 0; j < i; j++)
y[i] -= L[i][j] * y[j];
y[i] = y[i] / L[i][i];
}
}
inline void backward_elimination(const Matrix<double>& U, Vector<double>& x, const Vector<double>& y)
{
register int i, j, n = U.nrows();
x[n - 1] = y[n - 1] / U[n - 1][n - 1];
for (i = n - 2; i >= 0; i--)
{
x[i] = y[i];
for (j = i + 1; j < n; j++)
x[i] -= U[i][j] * x[j];
x[i] = x[i] / U[i][i];
}
}
void print_matrix(const char* name, const Matrix<double>& A, int n, int m)
{
std::ostringstream s;
std::string t;
if (n == -1)
n = A.nrows();
if (m == -1)
m = A.ncols();
s << name << ": " << std::endl;
for (int i = 0; i < n; i++)
{
s << " ";
for (int j = 0; j < m; j++)
s << A[i][j] << ", ";
s << std::endl;
}
t = s.str();
t = t.substr(0, t.size() - 3);
std::cout << t << std::endl;
}
template<typename T>
void print_vector(const char* name, const Vector<T>& v, int n)
{
std::ostringstream s;
std::string t;
if (n == -1)
n = v.size();
s << name << ": " << std::endl << " ";
for (int i = 0; i < n; i++)
{
s << v[i] << ", ";
}
t = s.str();
t = t.substr(0, t.size() - 2);
std::cout << t << std::endl;
}
}