/*
* Copyright: Copyright (c) MOSEK ApS, Denmark. All rights reserved.
*
* File:      qcqp_sdo_relaxation.cs
*
* Purpose:   Demonstrate how to use SDP to solve
*            convex relaxation of a mixed-integer QCQP
**/
using System;

using mosek.fusion;
using mosek;

namespace mosek.fusion.example
{
  public class qcqp_sdo_relaxation
  {
    public static void Main(string[] args)
    {
      Random rnd = new Random();
      // problem dimensions
      int n = 20;
      int m = 2 * n;

      // problem data
      double[] A = new double[m * n];
      double[] b = new double[m];
      double[] c = new double[n];
      double[] P = new double[n * n];
      double[] q = new double[n];

      for (int j = 0; j < n; j++) {
        for (int i = 0; i < m; i++) {
          double u1 = rnd.NextDouble(); //these are uniform(0,1) random doubles
          double u2 = rnd.NextDouble();
          A[i * n + j] = Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2);
        }
        c[j] = rnd.NextDouble();
      }

      // P = A^T A
      LinAlg.syrk(mosek.uplo.lo, mosek.transpose.yes,
                  n, m, 1.0, A, 0.0, P);
      for (int j = 0; j < n; j++) for (int i = j + 1; i < n; i++) P[i * n + j] = P[j * n + i];

      // q = -P c, b = A c
      LinAlg.gemv(mosek.transpose.no, n, n, -1.0, P, c, 0.0, q);
      LinAlg.gemv(mosek.transpose.no, m, n,  1.0, A, c, 0.0, b);

      // Solve the problems
      Model M = miqcqp_sdo_relaxation(n, Matrix.Dense(n, n, P), q);
      Model Mint = int_least_squares(n, Matrix.Dense(n, m, A).Transpose(), b);
      M.Solve();
      Mint.Solve();

      // rounded and optimal solution
      double[] xRound = M.GetVariable("Z").Slice(new int[] {0, n}, new int[] {n, n + 1}).Level();
      for (int i = 0; i < n; i++) xRound[i] = Math.Round(xRound[i]);
      double[] yRound = (double[]) b.Clone();
      double[] xOpt   = Mint.GetVariable("x").Level();
      double[] yOpt   = (double[]) b.Clone();
      LinAlg.gemv(mosek.transpose.no, m, n,  1.0, A, xRound, -1.0, yRound);        // Ax_round-b
      LinAlg.gemv(mosek.transpose.no, m, n,  1.0, A, xOpt, -1.0, yOpt);            // Ax_opt-b

      Console.WriteLine(M.GetSolverDoubleInfo("optimizerTime") + " " + Mint.GetSolverDoubleInfo("optimizerTime"));
      Console.WriteLine(Math.Sqrt(LinAlg.dot(m, yRound, yRound)) + " " +
                        Math.Sqrt(LinAlg.dot(m, yOpt, yOpt)));
    }

    public static Model miqcqp_sdo_relaxation(int n, Matrix P, double[] q) {
      Model M = new Model();

      Variable Z = M.Variable("Z", Domain.InPSDCone(n + 1));

      Variable X = Z.Slice(new int[] {0, 0}, new int[] {n, n});
      Variable x = Z.Slice(new int[] {0, n}, new int[] {n, n + 1});

      M.Constraint( Expr.Sub(X.Diag(), x), Domain.GreaterThan(0.0) );
      M.Constraint( Z.Index(n, n), Domain.EqualsTo(1.0) );

      M.Objective( ObjectiveSense.Minimize, Expr.Add(
                     Expr.Sum( Expr.MulElm( P, X ) ),
                     Expr.Mul( 2.0, Expr.Dot(x, q) )
                   ) );

      return M;
    }

    // A direct integer model for minimizing |Ax-b|
    public static Model int_least_squares(int n, Matrix A, double[] b) {
      Model M = new Model();

      Variable x = M.Variable("x", n, Domain.Integral(Domain.Unbounded()));
      Variable t = M.Variable("t", 1, Domain.Unbounded());

      M.Constraint( Expr.Vstack(t, Expr.Sub(Expr.Mul(A, x), b)), Domain.InQCone() );
      M.Objective( ObjectiveSense.Minimize, t );

      return M;
    }
  }
}
