/********************************************************
  Xpress-BCL Java Example Problems
  ================================

  file folioenumsol.java
  ``````````````````````
  Modeling a MIP problem 
  to perform portfolio optimization.

  Same model as in foliomip3.java.
  -- Using the solution enumerator --

  (c) 2009 Fair Isaac Corporation
      author: S.Heipcke, Y.Colombani, June 2009, rev. Dec. 2011
********************************************************/

import java.io.*;
import java.lang.*;
import java.util.*;
import com.dashoptimization.*;

public class folioenumsol
{
 static final String DATAFILE = "folio10.cdat";

 static final int MAXNUM = 7;          /* Max. number of different assets */
 static final double MAXRISK = 1.0/3;  /* Max. investment into high-risk values */
 static final double MINREG = 0.2;     /* Min. investment per geogr. region */
 static final double MAXREG = 0.5;     /* Max. investment per geogr. region */
 static final double MAXSEC = 0.25;    /* 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 */
 static XPRBprob p;


 public static void main(String[] args) throws IOException, XPRSexception
 { 
  int s,t,r,i;
  XPRB bcl;
  XPRBexpr Risk,Return,Cap,Num, LinkL, LinkU;
  XPRBexpr[] MinReg, MaxReg, LimSec;

  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("FolioMIP3");    /* 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", XPRB.PL, 0, MAXVAL);
   buy[s] = p.newVar("buy", XPRB.BV);
  }
   
/* Objective: total return */
  Return = new XPRBexpr();
  for(s=0;s<NSHARES;s++) Return.add(frac[s].mul(RET[s])); 
  p.setObj(Return);                  /* Set the objective function */

/* Limit the percentage of high-risk values */
  Risk = new XPRBexpr();
  for(s=0;s<NRISK;s++) Risk.add(frac[RISK[s]]); 
  p.newCtr(Risk.lEql(MAXRISK));

/* Limits on geographical distribution */
  MinReg = new XPRBexpr[NREGIONS];
  MaxReg = new XPRBexpr[NREGIONS];
  for(r=0;r<NREGIONS;r++)
  {
   MinReg[r] = new XPRBexpr();
   MaxReg[r] = new XPRBexpr();
   for(s=0;s<NSHARES;s++)
    if(LOC[r][s])
    {
     MinReg[r].add(frac[s]); 
     MaxReg[r].add(frac[s]);
    } 
    p.newCtr(MinReg[r].gEql(MINREG));
    p.newCtr(MaxReg[r].lEql(MAXREG));
  } 

/* Diversification across industry sectors */
  LimSec = new XPRBexpr[NTYPES];
  for(t=0;t<NTYPES;t++)
  {
   LimSec[t] = new XPRBexpr();
   for(s=0;s<NSHARES;s++)
    if(SEC[t][s]) LimSec[t].add(frac[s]); 
   p.newCtr(LimSec[t].lEql(MAXSEC));
  } 
 
/* Spend all the capital */
  Cap = new XPRBexpr();
  for(s=0;s<NSHARES;s++) Cap.add(frac[s]); 
  p.newCtr(Cap.eql(1));
 
/* Limit the total number of assets */
  Num = new XPRBexpr();
  for(s=0;s<NSHARES;s++) Num.add(buy[s]);
  p.newCtr(Num.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)));


/* Create a mip solution pool & enumerator */
  XPRSmipsolpool msp = new XPRSmipsolpool();
  XPRSmipsolenum mse = new XPRSmipsolenum();

/* Load matrix into the Optimizer, then hand over to solution enumerator */
  p.loadMat();

/* Disable heuristics to avoid duplicate solutions being stored */
  p.getXPRSprob().setIntControl(XPRS.HEURSTRATEGY, 0);

/* Solve the problem */
  IntHolder nMaxSols = new IntHolder(15);  // Max. number of solutions to return
  mse.maxim(p.getXPRSprob(), msp, new XPRSdefaultMipSolEnumHandler(), null,
            nMaxSols);	

/* Setup some resources to iterate through the solutions stored 
   in the MIP solution pool */
  int nCols = p.getXPRSprob().getIntAttrib(XPRS.ORIGINALCOLS);
  double[] xsol = new double[nCols];

/* Get the number of solutions and solution IDs from the mip solution pool */
  int nSols = mse.attributes().getSolutions();
  int[] iSolutionIds = new int[nSols];
  msp.getSolList(p.getXPRSprob(), XPRS.MSP_SOLPRB_OBJ, 1, 1, nSols,
                 iSolutionIds, null, null); 

  for(i=0; i<nSols; i++)
  {                              /* Get the solution */
   msp.getSol(iSolutionIds[i], null, xsol, 0, nCols-1, null);
   p.loadMIPSol(xsol);           /* Load the solution into BCL */
   print_sol(i+1);               /* Display the solution */
  }

/* Delete the problem */
  p.finalize();              
  p=null;
 }

/**************************Solution printing****************************/
 static void print_sol(int num)
 {
  int s;
  System.out.println("Solution " + num + ": Total return: " + p.getObjVal());
  for(s=0;s<NSHARES;s++) 
   if(buy[s].getSol()>0.5)
    System.out.println("  " + SHARES_n[s] + ": " + frac[s].getSol()*100 + 
     "% (" + buy[s].getSol() + ")");  
 }

/***********************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();
 }
} 
