/********************************************************
  Xpress-BCL Java Example Problems
  ================================

  file foliorep.java
  ``````````````````
  Modeling a MIP problem 
  to perform portfolio optimization.

  Same model as in foliomip3.java.
  -- Infeasible model parameter values --
  -- Retrieving IIS --

  (c) 2009 Fair Isaac Corporation
      author: S.Heipcke, June 2009, rev. Nov. 2014
********************************************************/

import java.io.*;
import java.lang.*;
import java.util.*;
import com.dashoptimization.*;

public class foliorep
{
 static final String DATAFILE = "folio10.cdat";

 static final int MAXNUM = 4;          /* Max. number of different assets */
 static final double MAXRISK = 1.0/3;  /* Max. investment into high-risk values */
 static final double MINREG = 0.3;     /* Min. investment per geogr. region */
 static final double MAXREG = 0.5;     /* Max. investment per geogr. region */
 static final double MAXSEC = 0.15;    /* Max. investment per ind. sector */
 static final double MAXVAL = 0.2;     /* Max. investment per share */
 static final double MINVAL = 0.1;     /* Min. investment per share */


 static int NSHARES;               /* Number of shares */
 static int NRISK;                 /* Number of high-risk shares */
 static int NREGIONS;              /* Number of geographical regions */
 static int NTYPES;                /* Number of share types */

 static double[] RET;              /* Estimated return in investment  */
 static int[] RISK;                /* High-risk values among shares */
 static boolean LOC[][];           /* Geogr. region of shares */
 static boolean SEC[][];           /* Industry sector of shares */

 static String SHARES_n[];
 static String REGIONS_n[];
 static String TYPES_n[];

 static XPRBvar[] frac;            /* Fraction of capital used per share */
 static XPRBvar[] buy;             /* 1 if asset is in portfolio, 0 otherwise */

 public static void main(String[] args) throws IOException
 { 
  int s,t,r;
  XPRB bcl;
  XPRBprob p;
  XPRBexpr LinkL, LinkU, le, le2;
  XPRBctr Risk,Return,Cap,Num;
  XPRBctr[] MinReg, MaxReg, LimSec;
  ArrayList<XPRBctr> AllCtrs = new ArrayList<XPRBctr>(); /* Array of ctrs that will be allowed to be relaxed */

  try
  {
   readData();                     /* Read data from file */
  }
  catch(IOException e)
  {
   System.err.println(e.getMessage());
   System.exit(1);
  }

  bcl = new XPRB();                /* Initialize BCL */
  p = bcl.newProb("FolioMIP3inf"); /* Create a new problem in BCL */
  XPRS.init();                     /* Initialize Xpress-Optimizer */

/* Create the decision variables */
  frac = new XPRBvar[NSHARES];
  buy = new XPRBvar[NSHARES];
  for(s=0;s<NSHARES;s++)
  {
   frac[s] = p.newVar("frac_"+SHARES_n[s], XPRB.PL, 0, MAXVAL);
   buy[s] = p.newVar("buy_"+SHARES_n[s], XPRB.BV);
  }
   
/* Objective: total return */
  le = new XPRBexpr();
  for(s=0;s<NSHARES;s++) le.add(frac[s].mul(RET[s])); 
  Return = p.newCtr("Return", le);
  p.setObj(le);                    /* Set the objective function */

/* Limit the percentage of high-risk values */
  le = new XPRBexpr();
  for(s=0;s<NRISK;s++) le.add(frac[RISK[s]]); 
  AllCtrs.add(Risk = p.newCtr("Risk", le.lEql(MAXRISK)));

/* Limits on geographical distribution */
  MinReg = new XPRBctr[NREGIONS];
  MaxReg = new XPRBctr[NREGIONS];
  for(r=0;r<NREGIONS;r++)
  {
   le = new XPRBexpr();
   le2 = new XPRBexpr();
   for(s=0;s<NSHARES;s++)
    if(LOC[r][s])
    {
     le.add(frac[s]); 
     le2.add(frac[s]);
    } 
    AllCtrs.add(MinReg[r] = p.newCtr("MinReg("+REGIONS_n[r]+")", le.gEql(MINREG)));
    AllCtrs.add(MaxReg[r] = p.newCtr("MaxReg("+REGIONS_n[r]+")", le2.lEql(MAXREG)));
  } 

/* Diversification across industry sectors */
  LimSec = new XPRBctr[NTYPES];
  for(t=0;t<NTYPES;t++)
  {
   le = new XPRBexpr();
   for(s=0;s<NSHARES;s++)
    if(SEC[t][s]) le.add(frac[s]); 
   AllCtrs.add(LimSec[t] = p.newCtr("LimSec("+TYPES_n[t]+")", le.lEql(MAXSEC)));
  } 
 
/* Spend all the capital */
  le = new XPRBexpr();
  for(s=0;s<NSHARES;s++) le.add(frac[s]); 
  Cap = p.newCtr("Cap", le.eql(1));
 
/* Limit the total number of assets */
  le = new XPRBexpr();
  for(s=0;s<NSHARES;s++) le.add(buy[s]);
  AllCtrs.add(Num = p.newCtr("Num", le.lEql(MAXNUM)));

/* Linking the variables */
  for(s=0;s<NSHARES;s++) p.newCtr(frac[s].lEql(buy[s].mul(MAXVAL)));
  for(s=0;s<NSHARES;s++) p.newCtr(frac[s].gEql(buy[s].mul(MINVAL)));

/* Solve the problem (LP) */
  p.setMsgLevel(1);
  p.setSense(XPRB.MAXIM);
  p.lpOptimize("");

  if (p.getLPStat()==XPRB.LP_INFEAS)
  {
   System.out.println("LP infeasible. Start infeasibility repair.");

   XPRSprob op = p.getXPRSprob();      /* Retrieve the Optimizer problem */

  /* Must use the weighted infeasibility repair method since
     only some constraints of each type may be relaxed */

/*
lrp: (affects = and <= rows)
   ax - aux_var  = b
   ax - aux_var <= b
grp: (affects = and >= rows)
   ax + aux_var  = b 
   ax + aux_var >= b
lbp:
   x_i + aux_var >= l
ubp:
   x_i - aux_var <= u 
*/

   int nrow, ncol;
   double[] lrp, grp, lbp, ubp;
   ncol=op.getIntAttrib(XPRS.ORIGINALCOLS);    
   nrow=op.getIntAttrib(XPRS.ORIGINALROWS);    
   lrp = new double[nrow]; 
   grp = new double[nrow]; 
   lbp = new double[ncol]; 
   ubp = new double[ncol]; 

   lrp[Risk.getRowNum()] = 1;
   for(r=0;r<NREGIONS;r++) lrp[MaxReg[r].getRowNum()] = 1;
   for(t=0;t<NTYPES;t++) lrp[LimSec[t].getRowNum()] = 1;
   lrp[Num.getRowNum()] = 1;
   for(r=0;r<NREGIONS;r++) grp[MinReg[r].getRowNum()] = 1;

   String rstat[] = {"relaxed optimum found", "relaxed problem infeasible",
    "relaxed problem unbounded", "solution nonoptimal for original objective",
    "error", "numerical instability"};
   IntHolder repstatus = new IntHolder();
   for (double delta = 0.001; delta<10; delta*=10) {
    op.repairWeightedInfeas(repstatus, lrp, grp, lbp, ubp, 'd', delta, "");
    System.out.println("delta = " + delta + ": Status: " +
                       rstat[repstatus.value]);
    if (repstatus.value==0) {
     p.sync(XPRB.XPRS_SOLMIP);
     print_sol_opt(p);
     print_violated(AllCtrs);
    }
    System.out.println();
   }
  } 

/* Delete the problem */
  p.finalize();              
  p=null;
 }

/**************************Solution printing****************************/

 static void print_sol_opt(XPRBprob p)
 {
  System.out.println("  Total return: " + p.getObjVal());
  for(int s=0; s<NSHARES; s++)
   if(buy[s].getSol()>0.5)
    System.out.printf("  %s: %g%% (%g)\n", SHARES_n[s], frac[s].getSol()*100, buy[s].getSol());
 }

 static void print_violated(ArrayList<XPRBctr> ctrs)
 {
  System.out.println(" Violated (relaxed) constraints:");
  for (XPRBctr c: ctrs) {
   String type;
   double viol, slack = c.getSlack();
   switch (c.getType()) {
    case XPRB.E: viol = Math.abs(slack); type = " ="; break;
    case XPRB.G: viol = slack;           type = ">="; break;
    case XPRB.L: viol = -slack;          type = "<="; break;
    default: System.out.println("  unexpected constraint type"); continue;
   }
   if (viol > 1e-6) System.out.printf("  %s constraint %s by %g\n", type, c.getName(), -slack);
  }
 }

/***********************Data input routines***************************/

/***************************/
/* Input a list of strings */
/***************************/
 private static String [] read_str_list(StreamTokenizer st) throws IOException
 {
  LinkedList<String> l=new LinkedList<String>();

  st.nextToken();                  /* Skip ':' */
  while(st.nextToken()==st.TT_WORD)
  {
   l.addLast(st.sval);
  }

  String a[]=new String[l.size()];
  l.toArray(a);
  return a;
 }

/************************/
/* Input a list of ints */
/************************/
 private static int [] read_int_list(StreamTokenizer st) throws IOException
 {
  LinkedList<Integer> l=new LinkedList<Integer>();

  st.nextToken();                  /* Skip ':' */
  while(st.nextToken()==st.TT_NUMBER)
  {
   l.addLast((int)st.nval);
  }

  int a[]=new int[l.size()];
  for(int i=0;i<l.size();i++)
   a[i]=((Integer)l.get(i)).intValue();
  return a;
 }

/****************************/
/* Input a table of doubles */
/****************************/
 private static void read_dbl_table(StreamTokenizer st,double tbl[]) throws IOException
 {
  int n=0;

  st.nextToken();                  /* Skip ':' */
  while(st.nextToken()==st.TT_NUMBER)
  {
   tbl[n++]=st.nval;
  }
 }

/************************************/
/* Input a sparse table of booleans */
/************************************/
 private static boolean [][] read_bool_table(StreamTokenizer st,int nrow,int ncol) throws IOException
 {
  int i;
  boolean tbl[][]=new boolean[nrow][ncol];

  st.nextToken();                  /* Skip ':' */
  for(int r=0;r<nrow;r++)
  {
   while(st.nextToken()==st.TT_NUMBER)
    tbl[r][(int)st.nval]=true;
  }
  return tbl;
 }

 private static void readData() throws IOException
 {
  int s;
  FileReader datafile=null;
  StreamTokenizer st=null;

  datafile=new FileReader(DATAFILE);/* Open the data file */
  st=new StreamTokenizer(datafile); /* Initialize the stream tokenizer */
  st.commentChar('!');              /* Use the character '!' for comments */
  st.eolIsSignificant(false);       /* Return end-of-line character */
  st.parseNumbers();                /* Read numbers as numbers (not strings) */

  while ( st.nextToken() == st.TT_WORD )
  {
   if ( st.sval.equals("SHARES") && NSHARES==0)
    { SHARES_n=read_str_list(st); NSHARES=SHARES_n.length; }
   else
   if ( st.sval.equals("REGIONS") && NREGIONS==0)
    { REGIONS_n=read_str_list(st); NREGIONS=REGIONS_n.length; }
   else
   if ( st.sval.equals("TYPES") && NTYPES==0)
    { TYPES_n=read_str_list(st); NTYPES=TYPES_n.length; }
   else
   if ( st.sval.equals("RISK") && NRISK==0)
    { RISK=read_int_list(st); NRISK=RISK.length; }
   else
   if ( st.sval.equals("RET") && NSHARES>0)
    {
     RET=new double[NSHARES];
     read_dbl_table(st,RET);
    }
   else
   if ( st.sval.equals("LOC") && NSHARES>0 && NREGIONS>0)
     LOC=read_bool_table(st,NREGIONS,NSHARES);
   else
   if ( st.sval.equals("SEC") && NSHARES>0 && NTYPES>0)
    SEC=read_bool_table(st,NTYPES,NSHARES);
   else
    break;
  }

/*
  for(int i=0;i<NREGIONS;i++) {
   for(int j=0;j<NSHARES;j++)
    System.out.print(" "+LOC[i][j]);
   System.out.println();
  }
*/
  datafile.close();
 }
} 
