(!******************************************************
   Mosel Example Problems
   ======================

   file folioenumsol.mos
   `````````````````````
   Modeling a MIP problem 
   to perform portfolio optimization.
   
   Same model as in foliomip3.mos.
   -- Using the solution enumerator --
   
  (c) 2008 Fair Isaac Corporation
      author: S.Heipcke, Dec. 2008, rev. Jan. 2023
*******************************************************!)

model "Portfolio optimization with MIP"
 uses "mmxprs"

 parameters
  MAXRISK = 1/3                     ! Max. investment into high-risk values
  MINREG = 0.2                      ! Min. investment per geogr. region
  MAXREG = 0.5                      ! Max. investment per geogr. region
  MAXSEC = 0.25                     ! Max. investment per ind. sector
  MAXVAL = 0.2                      ! Max. investment per share
  MINVAL = 0.1                      ! Min. investment per share
  MAXNUM = 7                        ! Max. number of different assets
  DATAFILE = "folio10.dat"          ! File with problem data
 end-parameters

 forward procedure print_sol

 declarations
  SHARES: set of string              ! Set of shares
  RISK: set of string                ! Set of high-risk values among shares
  REGIONS: set of string             ! Geographical regions
  TYPES: set of string               ! Share types (ind. sectors)
  LOC: array(REGIONS) of set of string ! Sets of shares per geogr. region
  RET: array(SHARES) of real         ! Estimated return in investment
  SEC: array(TYPES) of set of string ! Sets of shares per industry sector
 end-declarations

 initializations from DATAFILE
  RISK RET LOC SEC
 end-initializations

 declarations
  frac: array(SHARES) of mpvar      ! Fraction of capital used per share
  buy: array(SHARES) of mpvar       ! 1 if asset is in portfolio, 0 otherwise
 end-declarations

! Objective: total return
 Return:= sum(s in SHARES) RET(s)*frac(s) 

! Limit the percentage of high-risk values
 sum(s in RISK) frac(s) <= MAXRISK

! Limits on geographical distribution
 forall(r in REGIONS) do
  sum(s in LOC(r)) frac(s) >= MINREG
  sum(s in LOC(r)) frac(s) <= MAXREG
 end-do 

! Diversification across industry sectors
 forall(t in TYPES) sum(s in SEC(t)) frac(s) <= MAXSEC

! Spend all the capital
 sum(s in SHARES) frac(s) = 1
 
! Upper bounds on the investment per share
 forall(s in SHARES) frac(s) <= MAXVAL

! Limit the total number of assets
 sum(s in SHARES) buy(s) <= MAXNUM

 forall(s in SHARES) do
  buy(s) is_binary                  ! Turn variables into binaries
  frac(s) <= MAXVAL*buy(s)                 ! Linking the variables
  frac(s) >= MINVAL*buy(s)                 ! Linking the variables
 end-do

! Remove doubles from the solution pool, comparing solutions on their 
! discrete variables only
 setparam("XPRS_ENUMDUPLPOL",3)

! Alternatively: switch off MIP heuristics to avoid generation of doubles
! setparam("XPRS_HEUREMPHASIS", 0)

! Disable presolve operations that attempt to improve the efficiency by 
! cutting off MIP solutions from the feasible region
 setparam("XPRS_MIPDUALREDUCTIONS", 0)

! Set the max. number of solutions to store (default: 10)
 setparam("XPRS_enummaxsol", 25)

! Display Optimizer log
! setparam("XPRS_verbose", true)

! Solve the problem, enabling the solution enumerator
 maximize(XPRS_ENUM, Return)

! Print out all solutions saved by the enumerator
 forall(i in 1..getparam("XPRS_enumsols")) do
  selectsol(i)                      ! Select a solution from the pool
  writeln("Solution ", i)
  print_sol
 end-do

! Solution printing
 procedure print_sol
  writeln("Total return: ", getobjval)
  forall(s in SHARES | getsol(frac(s))>0)
   writeln(s, ": ", getsol(frac(s))*100, "% (", getsol(buy(s)), ")")
 end-procedure
end-model 
