/********************************************************
  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;
        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);
        }

        try (XPRBprob p = new XPRBprob("FolioMIP3inf"); /* Initialize BCL and create a new problem */
             XPRS xprs = new XPRS()) {                  /* 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();
                }
            }

        }
    }

    /**************************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();
    }
}
