//
//   Copyright: Copyright (c) MOSEK ApS, Denmark. All rights reserved.
//
//   File:      gp1.cs
//
//   Purpose:   Demonstrates how to solve a simple Geometric Program (GP)
//              cast into conic form with exponential cones and log-sum-exp.
//
//              Example from
//                https://gpkit.readthedocs.io/en/latest/examples.html//maximizing-the-volume-of-a-box
//
using System;
using mosek;

namespace mosek.example
{
  class msgclass : mosek.Stream
  {
    string prefix;
    public msgclass (string prfx)
    {
      prefix = prfx;
    }

    public override void streamCB (string msg)
    {
      Console.Write ("{0}{1}", prefix, msg);
    }
  }

  public class gp1
  {
    // Since the value of infinity is ignored, we define it solely
    // for symbolic purposes
    static double inf = 0.0;

    // maximize     h*w*d
    // subjecto to  2*(h*w + h*d) <= Awall
    //              w*d <= Afloor
    //              alpha <= h/w <= beta
    //              gamma <= d/w <= delta
    //
    // Variable substitutions:  h = exp(x), w = exp(y), d = exp(z).
    //
    // maximize     x+y+z
    // subject      log( exp(x+y+log(2/Awall)) + exp(x+z+log(2/Awall)) ) <= 0
    //                              y+z <= log(Afloor)
    //              log( alpha ) <= x-y <= log( beta )
    //              log( gamma ) <= z-y <= log( delta )
    public static double[] max_volume_box(double Aw, double Af, 
                                          double alpha, double beta, double gamma, double delta)
    {
      // Basic dimensions of our problem
      int numvar    = 3;  // Variables in original problem
      int x=0, y=1, z=2;  // Indices of variables
      int numcon    = 3;  // Linear constraints in original problem

      // Linear part of the problem
      double[] cval  = {1, 1, 1};
      int[]    asubi = {0, 0, 1, 1, 2, 2};
      int[]    asubj = {y, z, x, y, z, y};
      double[] aval  = {1.0, 1.0, 1.0, -1.0, 1.0, -1.0};
      boundkey[] bkc = {boundkey.up, boundkey.ra, boundkey.ra};
      double[] blc   = {-inf, Math.Log(alpha), Math.Log(gamma)};
      double[] buc   = {Math.Log(Af), Math.Log(beta), Math.Log(delta)};

      using (Task task = new Task())
      {
        // Directs the log task stream to the user specified
        // method task_msg_obj.stream
        task.set_Stream (mosek.streamtype.log, new msgclass (""));

        // Add variables and constraints
        task.appendvars(numvar);
        task.appendcons(numcon);

        // Objective is the sum of three first variables
        task.putobjsense(objsense.maximize);
        task.putcslice(0, numvar, cval);
        task.putvarboundsliceconst(0, numvar, boundkey.fr, -inf, inf);

        // Add the three linear constraints
        task.putaijlist(asubi, asubj, aval);
        task.putconboundslice(0, numvar, bkc, blc, buc);

        // Affine expressions appearing in affine conic constraints
        // in this order:
        // u1, u2, x+y+log(2/Awall), x+z+log(2/Awall), 1.0, u1+u2-1.0
        long numafe    = 6;
        int u1 = 3, u2 = 4;     // Indices of slack variables
        long[]   afeidx = {0, 1, 2, 2, 3, 3, 5, 5};
        int[]    varidx = {u1, u2, x, y, x, z, u1, u2};
        double[] fval   = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
        double[] gfull  = {0, 0, Math.Log(2/Aw), Math.Log(2/Aw), 1.0, -1.0};

        // New variables u1, u2
        task.appendvars(2);
        task.putvarboundsliceconst(u1, u2+1, boundkey.fr, -inf, inf);

        // Append affine expressions
        task.appendafes(numafe);
        task.putafefentrylist(afeidx, varidx, fval);
        task.putafegslice(0, numafe, gfull);

        // Two affine conic constraints
        long expdom = task.appendprimalexpconedomain();

        // (u1, 1, x+y+log(2/Awall)) \in EXP
        task.appendacc(expdom, new long[]{0, 4, 2}, null);

        // (u2, 1, x+z+log(2/Awall)) \in EXP
        task.appendacc(expdom, new long[]{1, 4, 3}, null);     

        // The constraint u1+u2-1 \in \ZERO is added also as an ACC
        task.appendacc(task.appendrzerodomain(1), new long[]{5}, null);

        // Solve and map to original h, w, d
        task.optimize();

        double[] xyz = task.getxxslice(soltype.itr, 0, numvar);
        double[] hwd = new double[numvar];
        for(int i = 0; i < numvar; i++) hwd[i] = Math.Exp(xyz[i]);
        return hwd;
      }
    }
    
    public static void Main(String[] args)
    {
      double Aw    = 200.0;
      double Af    = 50.0;
      double alpha = 2.0;
      double beta  = 10.0;
      double gamma = 2.0;
      double delta = 10.0;
      
      double[] hwd = max_volume_box(Aw, Af, alpha, beta, gamma, delta);

      Console.WriteLine("h={0:f4} w={1:f4} d={2:f4}", hwd[0], hwd[1], hwd[2]);
    }
  }
}
