/********************************************************
  Xpress-BCL Java Example Problems
  ================================

  file foliocb.java
  `````````````````
  Modeling a MIP problem 
  to perform portfolio optimization.

  Same model as in foliomip3.java.
  -- Defining an integer solution callback --

  (c) 2009 Fair Isaac Corporation
      author: S.Heipcke, Y.Colombani, May 2009, rev. Dec. 2011
********************************************************/

import java.io.*;
import java.lang.*;
import java.util.*;
import com.dashoptimization.*;

public class foliocb
{
 static final String DATAFILE = "folio10.cdat";

 static final int MAXNUM = 15;         /* 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 final String[] MIPSTATUS = {"not loaded", "not optimized", 
                      "LP optimized", "unfinished (no solution)", 
                      "unfinished (solution found)", "infeasible", "optimal",
                      "unbounded"};

 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 XPRSexception
 { 
  int s,t,r;
  XPRB bcl;
  XPRBprob p;
  XPRBexpr Risk,Return,Cap,Num, LinkL, LinkU;
  XPRBexpr[] MinReg, MaxReg, LimSec;
  IntSolCallback cb;

  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)));

/* Define an integer solution callback */
  cb = new IntSolCallback();
  p.getXPRSprob().addIntSolListener(cb,p);

/* Solve the problem */
  p.setSense(XPRB.MAXIM);
  p.mipOptimize("");

  System.out.println("Problem status: " + MIPSTATUS[p.getMIPStat()]);
 
/* Solution printing */
  System.out.println("Total return: " + p.getObjVal());
  for(s=0;s<NSHARES;s++) 
   if(buy[s].getSol()>0.5)
    System.out.println(s + ": " + frac[s].getSol()*100 + "% (" +
     buy[s].getSol() + ")");  

/* Delete the problem */
  p.finalize();              
  p=null;
 }

/**************************Solution printing****************************/

 static class IntSolCallback implements XPRSintSolListener
 {
  public void XPRSintSolEvent(XPRSprob oprob, Object data)
  {
   int num, s;
   XPRBprob bprob;   

   try {
    bprob = (XPRBprob)data;
    bprob.beginCB(oprob);
    num = oprob.getIntAttrib(XPRS.MIPSOLS);  /* Get number of the solution */
    bprob.sync(XPRB.XPRS_SOL);               /* Update BCL solution values */
  
    System.out.println("Solution " + num + ": Total return: " +
                       bprob.getObjVal());

    for(s=0;s<NSHARES;s++) 
     if(buy[s].getSol()>0.5)
      System.out.println("  " + s + ": " + frac[s].getSol()*100 + "% (" +
       buy[s].getSol() + ")");  
    bprob.endCB();
   }
   catch(XPRSprobException e) {
    System.out.println("Error " + e.getCode() + ": " + e.getMessage());
   }
  }
 }

/***********************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();
 }
} 
