Initializing help system before first use

Folio - Examples from 'Getting Started'


Type: Portfolio optimization
Rating: 3 (intermediate)
Description: Different versions of a portfolio optimization problem.
Basic modelling and solving tasks:
  • modeling and solving a small LP problem (FolioInit)
  • modeling and solving a small MIP problem with binary variables (FolioMip1)
  • modeling and solving a small MIP problem with semi-continuous variables (FolioMip2)
  • modeling and solving QP, MIQP, QCQP problems (FolioQP, FolioQC)
  • heuristic solution of a MIP problem (FolioHeuristic)
Advanced modeling and solving tasks:
  • enlarged version of the basic MIP model (Folio, to be used with data set folio10.cdat)
  • defining an integer solution callback (FolioCB, to be used with data set folio10.cdat)
  • retrieving IIS (FolioIIS, FolioMipIIS, to be used with data set folio10.cdat)
File(s): Folio.cpp, FolioCB.cpp, FolioHeuristic.cpp, FolioIIS.cpp, FolioInit.cpp, FolioMip1.cpp, FolioMip2.cpp, FolioMipIIS.cpp, FolioQC.cpp, FolioQP.cpp


Folio.cpp
// (c) 2024-2024 Fair Isaac Corporation

/**
 * Modeling a MIP problem to perform portfolio optimization. <br>
 * There are a number of shares available in which to invest. The problem is to
 * split the available capital between these shares to maximize return on
 * investment, while satisfying certain constraints on the portfolio:
 * <ul>
 * <li>A maximum of 7 distinct shares can be invest in.</li>
 * <li>Each share has an associated industry sector and geographic region, and
 * the capital may not be overly concentrated in any one sector or region.</li>
 * <li>Some of the shares are considered to be high-risk, and the maximum
 * investment in these shares is limited.</li>
 * </ul>
 */

#include <iostream>
#include <xpress.hpp>

using namespace xpress;
using namespace xpress::objects;
using xpress::objects::utils::scalarProduct;
using xpress::objects::utils::sum;

/** The file from which data for this example is read. */
char const *const DATAFILE = "folio10.cdat";

int const MAXNUM = 7;           /* Max. number of different assets */
double const MAXRISK = 1.0 / 3; /* Max. investment into high-risk values */
double const MINREG = 0.2;      /* Min. investment per geogr. region */
double const MAXREG = 0.5;      /* Max. investment per geogr. region */
double const MAXSEC = 0.25;     /* Max. investment per ind. sector */
double const MAXVAL = 0.2;      /* Max. investment per share */
double const MINVAL = 0.1;      /* Min. investment per share */

std::vector<double> RET;            /* Estimated return in investment */
std::vector<int> RISK;              /* High-risk values among shares */
std::vector<std::vector<bool>> LOC; /* Geogr. region of shares */
std::vector<std::vector<bool>> SEC; /* Industry sector of shares */

std::vector<std::string> SHARES;
std::vector<std::string> REGIONS;
std::vector<std::string> TYPES;

void readData();

int main() {
  readData(); // Read data from file

  XpressProblem prob;

  // Create the decision variables
  // Fraction of capital used per share
  std::vector<Variable> frac = prob.addVariables(SHARES.size())
                                   .withUB(MAXVAL)
                                   .withName("frac %d")
                                   .toArray();
  // 1 if asset is in portfolio, 0 otherwise
  std::vector<Variable> buy = prob.addVariables(SHARES.size())
                                  .withType(ColumnType::Binary)
                                  .withName("buy %d")
                                  .toArray();

  // Objective: total return
  prob.setObjective(scalarProduct(frac, RET), ObjSense::Maximize);

  // Limit the percentage of high-risk values
  prob.addConstraint(sum(RISK, [&](auto v) { return frac[v]; }) <= MAXRISK);

  // Limits on geographical distribution
  prob.addConstraints(REGIONS.size(), [&](auto r) {
    return sum(SHARES.size(),
               [&](auto s) { return (LOC[r][s] ? 1.0 : 0.0) * frac[s]; })
        .in(MINREG, MAXREG);
  });

  // Diversification across industry sectors
  prob.addConstraints(TYPES.size(), [&](auto t) {
    return sum(SHARES.size(), [&](auto s) {
             return (SEC[t][s] ? 1.0 : 0.0) * frac[s];
           }) <= MAXSEC;
  });

  // Spend all the capital
  prob.addConstraint(sum(frac) == 1);

  // Limit the total number of assets
  prob.addConstraint(sum(buy) <= MAXNUM);

  // Linking the variables
  for (unsigned s = 0; s < SHARES.size(); s++) {
    prob.addConstraint(frac[s] <= MAXVAL * buy[s]);
    prob.addConstraint(frac[s] >= MINVAL * buy[s]);
  }

  // Set a time limit of 10 seconds
  prob.controls.setTimeLimit(10.0);

  // Solve the problem
  prob.optimize("");

  std::cout << "Problem status: " << prob.attributes.getMipStatus()
            << std::endl;
  if (prob.attributes.getMipStatus() != MIPStatus::Solution &&
      prob.attributes.getMipStatus() != MIPStatus::Optimal)
    throw std::runtime_error("optimization failed with status " +
                             to_string(prob.attributes.getMipStatus()));

  // Solution printing
  std::cout << "Total return: " << prob.attributes.getObjVal() << std::endl;
  auto sol = prob.getSolution();
  for (unsigned s = 0; s < SHARES.size(); s++)
    if (buy[s].getValue(sol) > 0.5)
      std::cout << "  " << s << ": " << frac[s].getValue(sol) * 100 << "% ("
                << buy[s].getValue(sol) << ")" << std::endl;
  return 0;
}

// Minimalistic data parsing.
#include <fstream>
#include <iterator>

/**
 * Read a list of strings. Iterates <code>it</code> until a semicolon is
 * encountered or the iterator ends.
 *
 * @param it The token sequence to read.
 * @param conv  Function that converts a string to <code>T</code>.
 * @return A vector of all tokens before the first semicolon.
 */
template <typename T>
std::vector<T> readStrings(std::istream_iterator<std::string> &it,
                           std::function<T(std::string const &)> conv) {
  std::vector<T> result;
  while (it != std::istream_iterator<std::string>()) {
    std::string token = *it++;
    if (token.size() > 0 && token[token.size() - 1] == ';') {
      if (token.size() > 1) {
        result.push_back(conv(token.substr(0, token.size() - 1)));
      }
      break;
    } else {
      result.push_back(conv(token));
    }
  }
  return result;
}

/**
 * Read a sparse table of booleans. Allocates a <code>nrow</code> by
 * <code>ncol</code> boolean table and fills it by the sparse data from the
 * token sequence. <code>it</code> is assumed to hold <code>nrow</code>
 * sequences of indices, each of which is terminated by a semicolon. The indices
 * in those vectors specify the <code>true</code> entries in the corresponding
 * row of the table.
 *
 * @tparam R     Type of row count.
 * @tparam C     Type of column count.
 * @param it     Token sequence.
 * @param nrow   Number of rows in the table.
 * @param ncol   Number of columns in the table.
 * @return The boolean table.
 */
template<typename R,typename C>
std::vector<std::vector<bool>>
readBoolTable(std::istream_iterator<std::string> &it, R nrow, C ncol) {
  std::vector<std::vector<bool>> tbl(nrow, std::vector<bool>(ncol));
  for (R r = 0; r < nrow; r++) {
    for (auto i : readStrings<int>(it, [](auto &s) { return stoi(s); }))
      tbl[r][i] = true;
  }
  return tbl;
}

void readData() {
  std::string dataDir("../../data");
#ifdef _WIN32
  size_t len;
  char buffer[1024];
  if ( !getenv_s(&len, buffer, sizeof(buffer), "EXAMPLE_DATA_DIR") &&
       len && len < sizeof(buffer) )
    dataDir = buffer;
#else
  char const *envDir = std::getenv("EXAMPLE_DATA_DIR");
  if (envDir)
    dataDir = envDir;
#endif
  std::string dataFile = dataDir + "/" + DATAFILE;
  std::ifstream ifs(dataFile);
  if (!ifs)
    throw std::runtime_error("Could not open " + dataFile);
  std::stringstream data(std::string((std::istreambuf_iterator<char>(ifs)),
                                     (std::istreambuf_iterator<char>())));
  std::istream_iterator<std::string> it(data);
  while (it != std::istream_iterator<std::string>()) {
    std::string token = *it++;
    if (token == "SHARES:")
      SHARES = readStrings<std::string>(it, [](auto &s) { return s; });
    else if (token == "REGIONS:")
      REGIONS = readStrings<std::string>(it, [](auto &s) { return s; });
    else if (token == "TYPES:")
      TYPES = readStrings<std::string>(it, [](auto &s) { return s; });
    else if (token == "RISK:")
      RISK = readStrings<int>(it, [](auto &s) { return stoi(s); });
    else if (token == "RET:")
      RET = readStrings<double>(it, [](auto &s) { return stod(s); });
    else if (token == "LOC:")
      LOC = readBoolTable(it, REGIONS.size(), SHARES.size());
    else if (token == "SEC:")
      SEC = readBoolTable(it, TYPES.size(), SHARES.size());
  }
}

FolioCB.cpp
// (c) 2024-2024 Fair Isaac Corporation

/**
 * Modeling a MIP problem to perform portfolio optimization. -- Defining an
 * integer solution callback --
 */
#include <iostream>
#include <xpress.hpp>

using namespace xpress;
using namespace xpress::objects;
using xpress::objects::utils::scalarProduct;
using xpress::objects::utils::sum;

/** The file from which data for this example is read. */
char const *const DATAFILE = "folio10.cdat";

int const MAXNUM = 15;          /* Max. number of different assets */
double const MAXRISK = 1.0 / 3; /* Max. investment into high-risk values */
double const MINREG = 0.2;      /* Min. investment per geogr. region */
double const MAXREG = 0.5;      /* Max. investment per geogr. region */
double const MAXSEC = 0.25;     /* Max. investment per ind. sector */
double const MAXVAL = 0.2;      /* Max. investment per share */
double const MINVAL = 0.1;      /* Min. investment per share */

std::vector<double> RET;            /* Estimated return in investment */
std::vector<int> RISK;              /* High-risk values among shares */
std::vector<std::vector<bool>> LOC; /* Geogr. region of shares */
std::vector<std::vector<bool>> SEC; /* Industry sector of shares */

std::vector<std::string> SHARES;
std::vector<std::string> REGIONS;
std::vector<std::string> TYPES;

void printProblemStatus(XpressProblem const &prob) {
  std::cout << "Problem status:" << std::endl
            << "\tSolve status: " << prob.attributes.getSolveStatus()
            << std::endl
            << "\tSol status: " << prob.attributes.getSolStatus() << std::endl;
}

void printProblemSolution(XpressProblem const &prob,
                          std::vector<Variable> const &buy,
                          std::vector<Variable> const &frac, bool isCallback) {
  auto sol = isCallback ? prob.getCallbackSolution() : prob.getSolution();
  std::cout << "Total return: "
            << (isCallback ? prob.attributes.getLpObjVal()
                           : prob.attributes.getObjVal())
            << std::endl;
  for (unsigned i = 0; i < SHARES.size(); ++i) {
    if (buy[i].getValue(sol) > 0.5)
      std::cout << i << ": " << (100.0 * frac[i].getValue(sol)) << "%"
                << " (" << buy[i].getValue(sol) << ")" << std::endl;
  }
}

void readData();

int main() {
  readData();
  XpressProblem prob;
  // Output all messages.
  prob.callbacks.addMessageCallback(XpressProblem::console);

  /**** VARIABLES ****/
  std::vector<Variable> frac =
      prob.addVariables(SHARES.size())
          /* Fraction of capital used per share */
          .withName("frac_%d")
          /* Upper bounds on the investment per share */
          .withUB(MAXVAL)
          .toArray();

  std::vector<Variable> buy = prob.addVariables(SHARES.size())
                                  .withName("buy_%d")
                                  .withType(ColumnType::Binary)
                                  .toArray();

  /**** CONSTRAINTS ****/
  /* Limit the percentage of high-risk values */
  prob.addConstraint(sum(RISK.size(),
                         [&](auto i) { return frac[RISK[i]]; }) <=
                     MAXRISK);

  /* Limits on geographical distribution */
  prob.addConstraints(REGIONS.size(), [&](auto r) {
    return sum(SHARES.size(),
               [&](auto s) { return (LOC[r][s] ? 1.0 : 0.0) * frac[s]; })
        .in(MINREG, MAXREG);
  });

  /* Diversification across industry sectors */
  prob.addConstraints(TYPES.size(), [&](auto t) {
    return sum(SHARES.size(), [&](auto s) {
             return (SEC[t][s] ? 1.0 : 0.0) * frac[s];
           }) <= MAXSEC;
  });

  /* Spend all the capital */
  prob.addConstraint(sum(frac) == 1.0);

  /* Limit the total number of assets */
  prob.addConstraint(sum(buy) <= MAXNUM);

  /* Linking the variables */
  prob.addConstraints(SHARES.size(),
                      [&](auto i) { return frac[i] >= MINVAL * buy[i]; });
  prob.addConstraints(SHARES.size(),
                      [&](auto i) { return frac[i] <= MAXVAL * buy[i]; });

  /* Objective: maximize total return */
  prob.setObjective(scalarProduct(frac, RET), ObjSense::Maximize);

  /* Callback for each new integer solution found */
  prob.callbacks.addIntsolCallback(
      [&](auto &p) { printProblemSolution(p, buy, frac, true); });

  /* Solve */
  prob.optimize();

  /* Solution printing */
  printProblemStatus(prob);
  printProblemSolution(prob, buy, frac, false);

  return 0;
}

// Minimalistic data parsing.
#include <fstream>
#include <iterator>

/**
 * Read a list of strings. Iterates <code>it</code> until a semicolon is
 * encountered or the iterator ends.
 *
 * @param it The token sequence to read.
 * @param conv  Function that converts a string to <code>T</code>.
 * @return A vector of all tokens before the first semicolon.
 */
template <typename T>
std::vector<T> readStrings(std::istream_iterator<std::string> &it,
                           std::function<T(std::string const &)> conv) {
  std::vector<T> result;
  while (it != std::istream_iterator<std::string>()) {
    std::string token = *it++;
    if (token.size() > 0 && token[token.size() - 1] == ';') {
      if (token.size() > 1) {
        result.push_back(conv(token.substr(0, token.size() - 1)));
      }
      break;
    } else {
      result.push_back(conv(token));
    }
  }
  return result;
}

/**
 * Read a sparse table of booleans. Allocates a <code>nrow</code> by
 * <code>ncol</code> boolean table and fills it by the sparse data from the
 * token sequence. <code>it</code> is assumed to hold <code>nrow</code>
 * sequences of indices, each of which is terminated by a semicolon. The indices
 * in those vectors specify the <code>true</code> entries in the corresponding
 * row of the table.
 *
 * @tparam R     Type of row count.
 * @tparam C     Type of column count.
 * @param it     Token sequence.
 * @param nrow   Number of rows in the table.
 * @param ncol   Number of columns in the table.
 * @return The boolean table.
 */
template<typename R,typename C>
std::vector<std::vector<bool>>
readBoolTable(std::istream_iterator<std::string> &it, R nrow, C ncol) {
  std::vector<std::vector<bool>> tbl(nrow, std::vector<bool>(ncol));
  for (R r = 0; r < nrow; r++) {
    for (auto i : readStrings<int>(it, [](auto &s) { return stoi(s); }))
      tbl[r][i] = true;
  }
  return tbl;
}

void readData() {
  std::string dataDir("../../data");
#ifdef _WIN32
  size_t len;
  char buffer[1024];
  if ( !getenv_s(&len, buffer, sizeof(buffer), "EXAMPLE_DATA_DIR") &&
       len && len < sizeof(buffer) )
    dataDir = buffer;
#else
  char const *envDir = std::getenv("EXAMPLE_DATA_DIR");
  if (envDir)
    dataDir = envDir;
#endif
  std::string dataFile = dataDir + "/" + DATAFILE;
  std::ifstream ifs(dataFile);
  if (!ifs)
    throw std::runtime_error("Could not open " + dataFile);
  std::stringstream data(std::string((std::istreambuf_iterator<char>(ifs)),
                                     (std::istreambuf_iterator<char>())));
  std::istream_iterator<std::string> it(data);
  while (it != std::istream_iterator<std::string>()) {
    std::string token = *it++;
    if (token == "SHARES:")
      SHARES = readStrings<std::string>(it, [](auto &s) { return s; });
    else if (token == "REGIONS:")
      REGIONS = readStrings<std::string>(it, [](auto &s) { return s; });
    else if (token == "TYPES:")
      TYPES = readStrings<std::string>(it, [](auto &s) { return s; });
    else if (token == "RISK:")
      RISK = readStrings<int>(it, [](auto &s) { return stoi(s); });
    else if (token == "RET:")
      RET = readStrings<double>(it, [](auto &s) { return stod(s); });
    else if (token == "LOC:")
      LOC = readBoolTable(it, REGIONS.size(), SHARES.size());
    else if (token == "SEC:")
      SEC = readBoolTable(it, TYPES.size(), SHARES.size());
  }
}

FolioHeuristic.cpp
// (c) 2024-2024 Fair Isaac Corporation

/**
 * Modeling a small LP problem to perform portfolio optimization. -- Heuristic
 * solution
 */

#include <iostream>
#include <xpress.hpp>

using namespace xpress;
using namespace xpress::objects;
using xpress::objects::utils::scalarProduct;
using xpress::objects::utils::sum;

/* Max. number of different assets */
int const MAXNUM = 4;
/* Number of shares */
int const NSHARES = 10;
/* Number of high-risk shares */
int const NRISK = 5;
/* Number of North-American shares */
int const NNA = 4;
/* Estimated return in investment */
std::vector<double> const RET{5, 17, 26, 12, 8, 9, 7, 6, 31, 21};
/* High-risk values among shares */
std::vector<int> const RISK{1, 2, 3, 8, 9};
/* Shares issued in N.-America */
std::vector<int> const NA{0, 1, 2, 3};

class FolioHeuristic {
  /** Optimizer problem. */
  XpressProblem prob;
  /** Fraction of capital used per share */
  std::vector<Variable> frac;
  /** 1 if asset is in portfolio, 0 otherwise */
  std::vector<Variable> buy;

public:
  FolioHeuristic()
      : prob(), frac(prob.addVariables(NSHARES)
                         /* Fraction of capital used per share */
                         .withName("frac_%d")
                         /* Upper bounds on the investment per share */
                         .withUB(0.3)
                         .toArray()),
        buy(prob.addVariables(NSHARES)
                /* Fraction of capital used per share */
                .withName("buy_%d")
                .withType(ColumnType::Binary)
                .toArray()) {}

  /** Print current problem status. */
  void printProblemStatus() const {
    std::cout << "Problem status:" << std::endl
              << "\tSolve status: " << prob.attributes.getSolveStatus()
              << std::endl
              << "\tLP status: " << prob.attributes.getLpStatus() << std::endl
              << "\tMIP status: " << prob.attributes.getMipStatus() << std::endl
              << "\tSol status: " << prob.attributes.getSolStatus()
              << std::endl;
  }

  /** Print current solution.
   * @param solveFlag What kind of solution this is.
   */
  void printProblemSolution(std::string solveFlag) {
    std::cout << "Total return (" << solveFlag
              << "): " << prob.attributes.getObjVal() << std::endl;
    auto sol = prob.getSolution();
    for (int i = 0; i < NSHARES; ++i) {
      std::cout << frac[i].getName() << ": " << (100. * frac[i].getValue(sol))
                << "%"
                << " (" << buy[i].getValue(sol) << ")" << std::endl;
    }
  }

  /** Load the model into the internal data structures. */
  void model() {
    // Output all messages.
    prob.callbacks.addMessageCallback(XpressProblem::console);

    /**** CONSTRAINTS ****/
    /* Limit the percentage of high-risk values */
    prob.addConstraint(sum(NRISK, [&](auto i) { return frac[RISK[i]]; }) <=
                       1.0 / 3.0);

    /* Minimum amount of North-American values */
    prob.addConstraint(sum(NNA, [&](auto i) { return frac[NA[i]]; }) >= 0.5);

    /* Spend all the capital */
    prob.addConstraint(sum(frac) == 1.0);

    /* Limit the total number of assets */
    prob.addConstraint(sum(buy) <= MAXNUM);

    /* Linking the variables */
    prob.addConstraints(NSHARES, [&](auto i) { return frac[i] <= buy[i]; });

    /* Objective: maximize total return */
    prob.setObjective(scalarProduct(frac, RET), ObjSense::Maximize);
  }

  /** Solve resident model with a heuristic. */
  void solveHeuristic() {
    /* Disable automatic cuts */
    prob.controls.setCutStrategy(XPRS_CUTSTRATEGY_NONE);
    // Switch presolve off
    prob.controls.setPresolve(XPRS_PRESOLVE_NONE);
    prob.controls.setMipPresolve(0);
    /* Get feasibility tolerance */
    double tol = prob.controls.getFeasTol();

    /* Solve the LP-problem */
    prob.lpOptimize();

    /* Get Solution */
    auto sol = prob.getSolution();

    /* Basis information */
    std::vector<int> rowstat(prob.attributes.getRows());
    std::vector<int> colstat(prob.attributes.getCols());
    /* Save the current basis */
    prob.getBasis(rowstat, colstat);

    /*
     * Fix all variables `buy' for which `frac' is at 0 or at a relatively large
     * value
     */
    std::vector<double> fsol(NSHARES);
    for (int i = 0; i < NSHARES; ++i) {
      /* Get the solution values of `frac' */
      fsol[i] = frac[i].getValue(sol);
      if (fsol[i] < tol)
        buy[i].fix(0);
      else if (fsol[i] > 0.2 - tol)
        buy[i].fix(1);
    }

    /* Solve with the new bounds on 'buy' */
    prob.mipOptimize();

    printProblemStatus();
    printProblemSolution("Heuristic solution");

    /* Reset variables to their original bounds */
    for (int i = 0; i < NSHARES; ++i) {
      if ((fsol[i] < tol) || (fsol[i] > 0.2 - tol)) {
        buy[i].setLB(0);
        buy[i].setUB(1);
      }
    }

    /* Load basis */
    prob.loadBasis(rowstat, colstat);
  }

  /** Solve resident model with optimizer. */
  void optimize() { prob.optimize(); }
};

int main() {
  FolioHeuristic heur;
  heur.model();
  heur.solveHeuristic();

  FolioHeuristic exact;
  exact.model();
  exact.optimize();
  /* Solution printing */
  exact.printProblemStatus();
  exact.printProblemSolution("Exact Solve");

  return 0;
}

FolioIIS.cpp
// (c) 2024-2024 Fair Isaac Corporation

/**
 * Modeling a MIP problem to perform portfolio optimization.
 * Used infeasible model parameter values and illustrates retrieving IIS.
 */

#include <iostream>
#include <xpress.hpp>

using namespace xpress;
using namespace xpress::objects;
using xpress::objects::utils::scalarProduct;
using xpress::objects::utils::sum;

/** The file from which data for this example is read. */
char const *const DATAFILE = "folio10.cdat";

int const MAXNUM = 5;           /* Max. number of different assets */
double const MAXRISK = 1.0 / 3; /* Max. investment into high-risk values */
double const MINREG = 0.1;      /* Min. investment per geogr. region */
double const MAXREG = 0.2;      /* Max. investment per geogr. region */
double const MAXSEC = 0.1;      /* Max. investment per ind. sector */
double const MAXVAL = 0.2;      /* Max. investment per share */
double const MINVAL = 0.1;      /* Min. investment per share */

std::vector<double> RET;            /* Estimated return in investment */
std::vector<int> RISK;              /* High-risk values among shares */
std::vector<std::vector<bool>> LOC; /* Geogr. region of shares */
std::vector<std::vector<bool>> SEC; /* Industry sector of shares */

std::vector<std::string> SHARES;
std::vector<std::string> REGIONS;
std::vector<std::string> TYPES;

void readData();

void printProblemStatus(XpressProblem const &prob) {
  std::cout << "Problem status:" << std::endl
            << "\tSolve status: " << prob.attributes.getSolveStatus()
            << std::endl
            << "\tLP status: " << prob.attributes.getLpStatus() << std::endl
            << "\tSol status: " << prob.attributes.getSolStatus() << std::endl;
}

int main(void) {
  readData();
  XpressProblem prob;
  prob.callbacks.addMessageCallback(XpressProblem::console);

  /**** VARIABLES ****/
  /* Fraction of capital used per share */
  std::vector<Variable> frac =
      prob.addVariables(SHARES.size())
          /* Fraction of capital used per share */
          .withName([&](auto i) { return "frac" + SHARES[i]; })
          /* Upper bounds on the investment per share */
          .withUB(MAXVAL)
          .toArray();

  /* 1 if asset is in portfolio, 0 otherwise */
  std::vector<Variable> buy =
      prob.addVariables(SHARES.size())
          .withName([&](auto i) { return "buy_" + SHARES[i]; })
          .withType(ColumnType::Binary)
          .toArray();

  /**** CONSTRAINTS ****/
  /* Limit the percentage of high-risk values */
  prob.addConstraint(
      (sum(RISK.size(), [&](auto i) { return frac[RISK[i]]; }) <= MAXRISK)
          .setName("Risk"));

  /* Limits on geographical distribution */
  for (unsigned r = 0; r < REGIONS.size(); ++r) {
    Expression MinReg = sum(SHARES.size(), [&](auto s) {
      return (LOC[r][s] ? 1.0 : 0.0) * frac[s];
    });
    Expression MaxReg = sum(SHARES.size(), [&](auto s) {
      return (LOC[r][s] ? 1.0 : 0.0) * frac[s];
    });
    prob.addConstraint(
        (MinReg <= MINREG).setName("MinReg(" + REGIONS[r] + ")"));
    prob.addConstraint(
        (MaxReg <= MAXREG).setName("MaxReg(" + REGIONS[r] + ")"));
  }

  /* Diversification across industry sectors */
  for (unsigned t = 0; t < TYPES.size(); ++t) {
    Expression LimSec = sum(SHARES.size(), [&](auto s) {
      return (SEC[t][s] ? 1.0 : 0.0) * frac[s];
    });
    prob.addConstraint((LimSec <= MAXSEC).setName("LimSec(" + TYPES[t] + ")"));
  }

  /* Spend all the capital */
  prob.addConstraint((sum(frac) == 1.0).setName("Cap"));

  /* Limit the total number of assets */
  prob.addConstraint((sum(buy) >= MAXNUM).setName("MaxAssets"));

  /* Linking the variables */
  prob.addConstraints(SHARES.size(), [&](auto i) {
    return (frac[i] >= MINVAL * buy[i]).setName("link_lb_" + std::to_string(i));
  });
  prob.addConstraints(SHARES.size(), [&](auto i) {
    return (frac[i] <= MAXVAL * buy[i])
        .setName("link_ub_%d" + std::to_string(i));
  });

  /* Objective: maximize total return */
  prob.setObjective(scalarProduct(frac, RET), ObjSense::Maximize);

  /* Solve */
  prob.optimize();

  /* Solution printing */
  printProblemStatus(prob);
  if (prob.attributes.getSolStatus() == SolStatus::Infeasible) {
    std::cout << "LP infeasible. Retrieving IIS." << std::endl;
    // Check there is at least one IIS
    int status = prob.firstIIS(1);
    if (status != 0)
      throw std::runtime_error("firstIIS() failed with status " +
                               std::to_string(status));
    int iisIndex = 1; // First IIS has index 1
    do {
      XPRSProblem::IISStatusInfo info = prob.IISStatus();
      std::cout << "IIS has " << info.rowsizes[iisIndex] << " constraints"
                << " and " << info.colsizes[iisIndex] << " columns"
                << ", " << info.numinfeas[iisIndex] << " infeasibilities"
                << " with an infeasibility of " << info.suminfeas[iisIndex]
                << std::endl;
      IIS data = prob.getIIS(iisIndex);
      std::cout << "Variables in IIS:" << std::endl;
      for (IISVariable const &v : data.getVariables()) {
        // Note that the IISVariable class has more fields than
        // we print here. See the reference documentation for
        // details.
        std::cout << "\t" << v.getVariable().getName() << " " << v.getDomain()
                  << std::endl;
      }
      std::cout << "Constraints in IIS:" << std::endl;
      for (IISConstraint const &c : data.getConstraints()) {
        // Note that the IISVariable class has more fields than
        // we print here. See the reference documentation for
        // details.
        std::cout << "\t" << std::get<Inequality>(c.getConstraint()).getName()
                  << std::endl;
      }
      ++iisIndex; // Prepare for next IIS (if any)
    } while (prob.nextIIS() == 0);
  }

  return 0;
}

// Minimalistic data parsing.
#include <fstream>
#include <iterator>

/**
 * Read a list of strings. Iterates <code>it</code> until a semicolon is
 * encountered or the iterator ends.
 *
 * @param it The token sequence to read.
 * @param conv  Function that converts a string to <code>T</code>.
 * @return A vector of all tokens before the first semicolon.
 */
template <typename T>
std::vector<T> readStrings(std::istream_iterator<std::string> &it,
                           std::function<T(std::string const &)> conv) {
  std::vector<T> result;
  while (it != std::istream_iterator<std::string>()) {
    std::string token = *it++;
    if (token.size() > 0 && token[token.size() - 1] == ';') {
      if (token.size() > 1) {
        result.push_back(conv(token.substr(0, token.size() - 1)));
      }
      break;
    } else {
      result.push_back(conv(token));
    }
  }
  return result;
}

/**
 * Read a sparse table of booleans. Allocates a <code>nrow</code> by
 * <code>ncol</code> boolean table and fills it by the sparse data from the
 * token sequence. <code>it</code> is assumed to hold <code>nrow</code>
 * sequences of indices, each of which is terminated by a semicolon. The indices
 * in those vectors specify the <code>true</code> entries in the corresponding
 * row of the table.
 *
 * @tparam R     Type of row count.
 * @tparam C     Type of column count.
 * @param it     Token sequence.
 * @param nrow   Number of rows in the table.
 * @param ncol   Number of columns in the table.
 * @return The boolean table.
 */
template<typename R,typename C>
std::vector<std::vector<bool>>
readBoolTable(std::istream_iterator<std::string> &it, R nrow, C ncol) {
  std::vector<std::vector<bool>> tbl(nrow, std::vector<bool>(ncol));
  for (R r = 0; r < nrow; r++) {
    for (auto i : readStrings<int>(it, [](auto &s) { return stoi(s); }))
      tbl[r][i] = true;
  }
  return tbl;
}

void readData() {
  std::string dataDir("../../data");
#ifdef _WIN32
  size_t len;
  char buffer[1024];
  if ( !getenv_s(&len, buffer, sizeof(buffer), "EXAMPLE_DATA_DIR") &&
       len && len < sizeof(buffer) )
    dataDir = buffer;
#else
  char const *envDir = std::getenv("EXAMPLE_DATA_DIR");
  if (envDir)
    dataDir = envDir;
#endif
  std::string dataFile = dataDir + "/" + DATAFILE;
  std::ifstream ifs(dataFile);
  if (!ifs)
    throw std::runtime_error("Could not open " + dataFile);
  std::stringstream data(std::string((std::istreambuf_iterator<char>(ifs)),
                                     (std::istreambuf_iterator<char>())));
  std::istream_iterator<std::string> it(data);
  while (it != std::istream_iterator<std::string>()) {
    std::string token = *it++;
    if (token == "SHARES:")
      SHARES = readStrings<std::string>(it, [](auto &s) { return s; });
    else if (token == "REGIONS:")
      REGIONS = readStrings<std::string>(it, [](auto &s) { return s; });
    else if (token == "TYPES:")
      TYPES = readStrings<std::string>(it, [](auto &s) { return s; });
    else if (token == "RISK:")
      RISK = readStrings<int>(it, [](auto &s) { return stoi(s); });
    else if (token == "RET:")
      RET = readStrings<double>(it, [](auto &s) { return stod(s); });
    else if (token == "LOC:")
      LOC = readBoolTable(it, REGIONS.size(), SHARES.size());
    else if (token == "SEC:")
      SEC = readBoolTable(it, TYPES.size(), SHARES.size());
  }
}

FolioInit.cpp
// (c) 2024-2024 Fair Isaac Corporation

/** Modeling a small LP problem to perform portfolio optimization. */

#include <iostream>
#include <xpress.hpp>

using namespace xpress;
using namespace xpress::objects;
using xpress::objects::utils::scalarProduct;
using xpress::objects::utils::sum;

/* Number of shares */
int const NSHARES = 10;
/* Number of high-risk shares */
int const NRISK = 5;
/* Number of North-American shares */
int const NNA = 4;
/* Estimated return in investment */
std::vector<double> const RET{5, 17, 26, 12, 8, 9, 7, 6, 31, 21};
/* High-risk values among shares */
std::vector<int> RISK{1, 2, 3, 8, 9};
/* Shares issued in N.-America */
std::vector<int> NA{0, 1, 2, 3};

void printProblemStatus(XpressProblem const &prob) {
  std::cout << "Problem status:" << std::endl
            << "\tSolve status: " << prob.attributes.getSolveStatus()
            << std::endl
            << "\tSol status: " << prob.attributes.getSolStatus() << std::endl;
}

int main(void) {
  XpressProblem prob;
  // Output all messages.
  prob.callbacks.addMessageCallback(XpressProblem::console);

  /**** VARIABLES ****/
  std::vector<Variable> frac =
      prob.addVariables(NSHARES)
          /* Fraction of capital used per share */
          .withName("frac_%d")
          /* Upper bounds on the investment per share */
          .withUB(0.3)
          .toArray();

  /**** CONSTRAINTS ****/
  /* Limit the percentage of high-risk values */
  prob.addConstraint(sum(NRISK, [&](auto i) { return frac[RISK[i]]; }) <=
                     1.0 / 3.0)
      .setName("Risk");

  /* Minimum amount of North-American values */
  prob.addConstraint(sum(NNA, [&](auto i) { return frac[NA[i]]; }) >= 0.5)
      .setName("NA");

  /* Spend all the capital */
  prob.addConstraint(sum(frac) == 1.0).setName("Cap");

  /* Objective: maximize total return */
  prob.setObjective(scalarProduct(frac, RET), ObjSense::Maximize);

  /* Solve */
  prob.optimize();

  /* Solution printing */
  printProblemStatus(prob);
  std::cout << "Total return: " << prob.attributes.getObjVal() << std::endl;
  auto sol = prob.getSolution();
  for (int i = 0; i < NSHARES; ++i) {
    std::cout << frac[i].getName() << ": " << (100.0 * frac[i].getValue(sol))
              << "%" << std::endl;
  }

  return 0;
}

FolioMip1.cpp
// (c) 2024-2024 Fair Isaac Corporation

/**
 * Modeling a small LP problem to perform portfolio optimization. -- Limiting
 * the total number of assets
 */

#include <iostream>
#include <xpress.hpp>

using namespace xpress;
using namespace xpress::objects;
using xpress::objects::utils::scalarProduct;
using xpress::objects::utils::sum;

/* Max. number of different assets */
int const MAXNUM = 4;
/* Number of shares */
int const NSHARES = 10;
/* Number of high-risk shares */
int const NRISK = 5;
/* Number of North-American shares */
int const NNA = 4;
/* Estimated return in investment */
std::vector<double> const RET{5, 17, 26, 12, 8, 9, 7, 6, 31, 21};
/* High-risk values among shares */
std::vector<int> RISK{1, 2, 3, 8, 9};
/* Shares issued in N.-America */
std::vector<int> NA{0, 1, 2, 3};

void printProblemStatus(XpressProblem const &prob) {
  std::cout << "Problem status:" << std::endl
            << "\tSolve status: " << prob.attributes.getSolveStatus()
            << std::endl
            << "\tSol status: " << prob.attributes.getSolStatus() << std::endl;
}

int main() {
  XpressProblem prob;
  // Output all messages.
  prob.callbacks.addMessageCallback(XpressProblem::console);

  /**** VARIABLES ****/
  /* Fraction of capital used per share */
  std::vector<Variable> frac =
      prob.addVariables(NSHARES)
          .withName("frac_%d")
          /* Upper bounds on the investment per share */
          .withUB(0.3)
          .toArray();

  /* Fraction of capital used per share */
  std::vector<Variable> buy = prob.addVariables(NSHARES)
                                  .withName("buy_%d")
                                  .withType(ColumnType::Binary)
                                  .toArray();

  /**** CONSTRAINTS ****/
  /* Limit the percentage of high-risk values */
  prob.addConstraint(sum(NRISK, [&](auto i) { return frac[RISK[i]]; }) <=
                     1.0 / 3.0)
      .setName("Risk");

  /* Minimum amount of North-American values */
  prob.addConstraint(sum(NNA, [&](auto i) { return frac[NA[i]]; }) >= 0.5)
      .setName("NA");

  /* Spend all the capital */
  prob.addConstraint(sum(frac) == 1.0).setName("Cap");

  /* Limit the total number of assets */
  prob.addConstraint(sum(buy) <= MAXNUM).setName("MaxAssets");

  /* Linking the variables */
  /* frac .<= buy */
  prob.addConstraints(NSHARES, [&](auto i) {
    return (frac[i] <= buy[i]).setName("link_" + std::to_string(i));
  });

  /* Objective: maximize total return */
  prob.setObjective(scalarProduct(frac, RET), ObjSense::Maximize);

  /* Solve */
  prob.optimize();

  /* Solution printing */
  printProblemStatus(prob);
  std::cout << "Total return: " << prob.attributes.getObjVal() << std::endl;
  auto sol = prob.getSolution();
  for (int i = 0; i < NSHARES; ++i)
    std::cout << frac[i].getName() << ": " << (100.0 * frac[i].getValue(sol))
              << "%"
              << " (" << buy[i].getValue(sol) << ")" << std::endl;
  return 0;
}

FolioMip2.cpp
// (c) 2024-2024 Fair Isaac Corporation

/**
 * Modeling a small LP problem to perform portfolio optimization. -- Imposing a
 * minimum investment per share.
 */

#include <iostream>
#include <xpress.hpp>

using namespace xpress;
using namespace xpress::objects;
using xpress::objects::utils::scalarProduct;
using xpress::objects::utils::sum;

/* Number of shares */
int const NSHARES = 10;
/* Number of high-risk shares */
int const NRISK = 5;
/* Number of North-American shares */
int const NNA = 4;
/* Estimated return in investment */
std::vector<double> RET{5, 17, 26, 12, 8, 9, 7, 6, 31, 21};
/* High-risk values among shares */
std::vector<int> RISK{1, 2, 3, 8, 9};
/* Shares issued in N.-America */
std::vector<int> NA{0, 1, 2, 3};

void printProblemStatus(XpressProblem const &prob) {
  std::cout << "Problem status:" << std::endl
            << "\tSolve status: " << prob.attributes.getSolveStatus()
            << std::endl
            << "\tSol status: " << prob.attributes.getSolStatus() << std::endl;
}

int main(void) {
  XpressProblem prob;
  // Output all messages.
  prob.callbacks.addMessageCallback(XpressProblem::console);

  /**** VARIABLES ****/
  std::vector<Variable> frac =
      prob.addVariables(NSHARES)
          /* Fraction of capital used per share */
          .withName("frac_%d")
          .withType(ColumnType::SemiContinuous)
          /* Upper bounds on the investment per share */
          .withUB(0.3)
          /* Investment limit */
          .withLimit(0.1)
          .toArray();

  /**** CONSTRAINTS ****/
  /* Limit the percentage of high-risk values */
  prob.addConstraint(sum(NRISK, [&](auto i) { return frac[RISK[i]]; }) <=
                     1.0 / 3.0);

  /* Minimum amount of North-American values */
  prob.addConstraint(sum(NNA, [&](auto i) { return frac[NA[i]]; }) >= 0.5);

  /* Spend all the capital */
  prob.addConstraint(sum(frac) == 1.0);

  /* Objective: maximize total return */
  prob.setObjective(scalarProduct(frac, RET), ObjSense::Maximize);

  /* Solve */
  prob.optimize();

  /* Solution printing */
  printProblemStatus(prob);
  std::cout << "Total return: " << prob.attributes.getObjVal() << std::endl;
  auto sol = prob.getSolution();
  for (int i = 0; i < NSHARES; ++i)
    std::cout << frac[i].getName() << " : " << 100.0 * frac[i].getValue(sol)
              << "%" << std::endl;
  return 0;
}

FolioMipIIS.cpp
// (c) 2024-2024 Fair Isaac Corporation

/**
 * Modeling a MIP problem to perform portfolio optimization.
 * Same model as in FolioMip3.java.
 * Uses infeasible model parameter values and illustrates retrieving MIIS.
 */

#include <iostream>
#include <xpress.hpp>

using namespace xpress;
using namespace xpress::objects;
using xpress::objects::utils::scalarProduct;
using xpress::objects::utils::sum;

/** The file from which data for this example is read. */
char const *const DATAFILE = "folio10.cdat";

int const MAXNUM = 5;           /* Max. number of different assets */
double const MAXRISK = 1.0 / 4; /* Max. investment into high-risk values */
double const MINREG = 0.1;      /* Min. investment per geogr. region */
double const MAXREG = 0.25;     /* Max. investment per geogr. region */
double const MAXSEC = 0.15;     /* Max. investment per ind. sector */
double const MAXVAL = 0.225;    /* Max. investment per share */
double const MINVAL = 0.1;      /* Min. investment per share */

std::vector<double> RET;            /* Estimated return in investment */
std::vector<int> RISK;              /* High-risk values among shares */
std::vector<std::vector<bool>> LOC; /* Geogr. region of shares */
std::vector<std::vector<bool>> SEC; /* Industry sector of shares */

std::vector<std::string> SHARES;
std::vector<std::string> REGIONS;
std::vector<std::string> TYPES;

void printProblemStatus(XpressProblem const &prob) {
  std::cout << "Problem status:" << std::endl
            << "\tSolve status: " << prob.attributes.getSolveStatus()
            << std::endl
            << "\tLP status: " << prob.attributes.getLpStatus() << std::endl
            << "\tMIP status: " << prob.attributes.getMipStatus() << std::endl
            << "\tSol status: " << prob.attributes.getSolStatus() << std::endl;
}

void readData();

int main() {
  readData();
  XpressProblem prob;
  // Output all messages.
  prob.callbacks.addMessageCallback(XpressProblem::console);

  /**** VARIABLES ****/
  /* Fraction of capital used per share */
  std::vector<Variable> frac =
      prob.addVariables(SHARES.size())
          .withName("frac_%d")
          /* Upper bounds on the investment per share */
          .withLB(0.0)
          .withUB(MAXVAL)
          .toArray();

  std::vector<Variable> buy = prob.addVariables(SHARES.size())
                                  .withName("buy_%d")
                                  .withType(ColumnType::Binary)
                                  .toArray();

  /**** CONSTRAINTS ****/
  /* Limit the percentage of high-risk values */
  prob.addConstraint(sum(RISK.size(),
                         [&](auto i) { return frac[RISK[i]]; }) <=
                     MAXRISK)
      .setName("Risk");

  /* Limits on geographical distribution */
  prob.addConstraints(REGIONS.size(), [&](auto r) {
    return sum(SHARES.size(),
               [&](auto s) { return (LOC[r][s] ? 1.0 : 0.0) * frac[s]; })
        .in(MINREG, MAXREG)
        .setName("MinMaxReg_" + REGIONS[r]);
  });

  /* Diversification across industry sectors */
  prob.addConstraints(TYPES.size(), [&](auto t) {
    return sum(SHARES.size(),
               [&](auto s) { return (SEC[t][s] ? 1.0 : 0.0) * frac[s]; })
        .in(0.0, MAXSEC)
        .setName("LimSec(" + TYPES[t] + ")");
  });

  /* Spend all the capital */
  prob.addConstraint(sum(frac) == 1.0).setName("Cap");

  /* Limit the total number of assets */
  prob.addConstraint(sum(buy) <= MAXNUM).setName("MaxAssets");

  /* Linking the variables */
  prob.addConstraints(SHARES.size(), [&](auto i) {
    return (frac[i] >= MINVAL * buy[i]).setName("link_lb_" + std::to_string(i));
  });
  prob.addConstraints(SHARES.size(), [&](auto i) {
    return (frac[i] <= MAXVAL * buy[i]).setName("link_ub_" + std::to_string(i));
  });

  /* Objective: maximize total return */
  prob.setObjective(scalarProduct(frac, RET), ObjSense::Maximize);

  /* Solve */
  prob.optimize();

  /* Solution printing */
  printProblemStatus(prob);
  if (prob.attributes.getSolStatus() == SolStatus::Infeasible) {
    std::cout << "MIP infeasible. Retrieving IIS." << std::endl;
    // Check there is at least one IIS
    int status = prob.firstIIS(1);
    if (status != 0)
      throw std::runtime_error("firstIIS() failed with status " +
                               std::to_string(status));
    XPRSProblem::IISStatusInfo info = prob.IISStatus();
    std::cout << "IIS has " << info.rowsizes[1] << " constraints and "
              << info.colsizes[1] << " columns, " << info.numinfeas[1]
              << " infeasibilities with an infeasibility of "
              << info.suminfeas[1] << std::endl;
    IIS data = prob.getIIS(1);
    std::cout << "Variables in IIS:" << std::endl;
    for (auto &v : data.getVariables()) {
      // Note that the IISVariable class has more fields than
      // we print here. See the reference documentation for
      // details.
      std::cout << "\t" << v.getVariable().getName() << " " << v.getDomain()
                << std::endl;
    }
    std::cout << "Constraints in IIS:" << std::endl;
    for (auto &c : data.getConstraints()) {
      // Note that the IISVariable class has more fields than
      // we print here. See the reference documentation for
      // details.
      std::cout << "\t" << std::get<Inequality>(c.getConstraint()).getName()
                << std::endl;
    }
  }

  return 0;
}

// Minimalistic data parsing.
#include <fstream>
#include <iterator>

/**
 * Read a list of strings. Iterates <code>it</code> until a semicolon is
 * encountered or the iterator ends.
 *
 * @param it The token sequence to read.
 * @param conv  Function that converts a string to <code>T</code>.
 * @return A vector of all tokens before the first semicolon.
 */
template <typename T>
std::vector<T> readStrings(std::istream_iterator<std::string> &it,
                           std::function<T(std::string const &)> conv) {
  std::vector<T> result;
  while (it != std::istream_iterator<std::string>()) {
    std::string token = *it++;
    if (token.size() > 0 && token[token.size() - 1] == ';') {
      if (token.size() > 1) {
        result.push_back(conv(token.substr(0, token.size() - 1)));
      }
      break;
    } else {
      result.push_back(conv(token));
    }
  }
  return result;
}

/**
 * Read a sparse table of booleans. Allocates a <code>nrow</code> by
 * <code>ncol</code> boolean table and fills it by the sparse data from the
 * token sequence. <code>it</code> is assumed to hold <code>nrow</code>
 * sequences of indices, each of which is terminated by a semicolon. The indices
 * in those vectors specify the <code>true</code> entries in the corresponding
 * row of the table.
 *
 * @tparam R     Type of row count.
 * @tparam C     Type of column count.
 * @param it     Token sequence.
 * @param nrow   Number of rows in the table.
 * @param ncol   Number of columns in the table.
 * @return The boolean table.
 */
template<typename R,typename C>
std::vector<std::vector<bool>>
readBoolTable(std::istream_iterator<std::string> &it, R nrow, C ncol) {
  std::vector<std::vector<bool>> tbl(nrow, std::vector<bool>(ncol));
  for (R r = 0; r < nrow; r++) {
    for (auto i : readStrings<int>(it, [](auto &s) { return stoi(s); }))
      tbl[r][i] = true;
  }
  return tbl;
}

void readData() {
  std::string dataDir("../../data");
#ifdef _WIN32
  size_t len;
  char buffer[1024];
  if ( !getenv_s(&len, buffer, sizeof(buffer), "EXAMPLE_DATA_DIR") &&
       len && len < sizeof(buffer) )
    dataDir = buffer;
#else
  char const *envDir = std::getenv("EXAMPLE_DATA_DIR");
  if (envDir)
    dataDir = envDir;
#endif
  std::string dataFile = dataDir + "/" + DATAFILE;
  std::ifstream ifs(dataFile);
  if (!ifs)
    throw std::runtime_error("Could not open " + dataFile);
  std::stringstream data(std::string((std::istreambuf_iterator<char>(ifs)),
                                     (std::istreambuf_iterator<char>())));
  std::istream_iterator<std::string> it(data);
  while (it != std::istream_iterator<std::string>()) {
    std::string token = *it++;
    if (token == "SHARES:")
      SHARES = readStrings<std::string>(it, [](auto &s) { return s; });
    else if (token == "REGIONS:")
      REGIONS = readStrings<std::string>(it, [](auto &s) { return s; });
    else if (token == "TYPES:")
      TYPES = readStrings<std::string>(it, [](auto &s) { return s; });
    else if (token == "RISK:")
      RISK = readStrings<int>(it, [](auto &s) { return stoi(s); });
    else if (token == "RET:")
      RET = readStrings<double>(it, [](auto &s) { return stod(s); });
    else if (token == "LOC:")
      LOC = readBoolTable(it, REGIONS.size(), SHARES.size());
    else if (token == "SEC:")
      SEC = readBoolTable(it, TYPES.size(), SHARES.size());
  }
}

FolioQC.cpp
// (c) 2024-2024 Fair Isaac Corporation

/**
 * Modeling a small QCQP problem to perform portfolio optimization. -- Maximize
 * return with limit on variance ---
 */

#include <iostream>
#include <xpress.hpp>

using namespace xpress;
using namespace xpress::objects;
using xpress::objects::utils::scalarProduct;
using xpress::objects::utils::sum;

/** The file from which data for this example is read. */
char const *const DATAFILE = "/foliocppqp.dat";
/** Max. allowed variance */
double const MAXVAR = 0.55;
/** Number of shares */
int const NSHARES = 10;
/** Number of North-American shares */
int const NNA = 4;
/** Estimated return in investment */
std::vector<double> const RET{5, 17, 26, 12, 8, 9, 7, 6, 31, 21};
/** Shares issued in N.-America */
std::vector<int> const NA{0, 1, 2, 3};
/** Variance/covariance matrix of estimated returns */
std::vector<std::vector<double>> VAR;

void printProblemStatus(XpressProblem const &prob) {
  std::cout << "Problem status:" << std::endl
            << "\tSolve status: " << prob.attributes.getSolveStatus()
            << std::endl
            << "\tLP status: " << prob.attributes.getLpStatus() << std::endl
            << "\tMIP status: " << prob.attributes.getMipStatus() << std::endl
            << "\tSol status: " << prob.attributes.getSolStatus() << std::endl;
}

void readData();

int main() {
  readData();
  XpressProblem prob;
  // Output all messages.
  prob.callbacks.addMessageCallback(XpressProblem::console);

  /**** VARIABLES ****/
  // Fraction of capital used per share
  std::vector<Variable> frac =
      prob.addVariables(NSHARES)
          .withName("frac_%d")
          /* Upper bounds on the investment per share */
          .withUB(0.3)
          .toArray();

  /**** CONSTRAINTS ****/
  // Limit variance
  Expression variance = sum(NSHARES, [&](auto s) {
    return sum(NSHARES, [&](auto t) {
      // v * fs * ft
      return VAR[s][t] * frac[s] * frac[t];
    });
  });
  prob.addConstraint(variance <= MAXVAR).setName("Variance");

  // Minimum amount of North-American values
  prob.addConstraint(sum(NNA, [&](auto i) { return frac[NA[i]]; }) >= 0.5)
      .setName("NA");

  // Spend all the capital
  prob.addConstraint(sum(frac) == 1.0).setName("Cap");

  // Objective: maximize total return
  prob.setObjective(scalarProduct(frac, RET), ObjSense::Maximize);

  // Solve
  prob.optimize();

  // Solution printing
  printProblemStatus(prob);
  std::cout << "With of max variance of " << MAXVAR << " total return is "
            << prob.attributes.getObjVal() << std::endl;
  auto sol = prob.getSolution();
  for (int i = 0; i < NSHARES; ++i)
    std::cout << frac[i].getName() << " : " << (100.0 * frac[i].getValue(sol))
              << "%" << std::endl;
  return 0;
}

#include <fstream>
#include <sstream>
#include <cctype>
void readData() {
  std::string dataDir("../../data");
#ifdef _WIN32
  size_t len;
  char buffer[1024];
  if ( !getenv_s(&len, buffer, sizeof(buffer), "EXAMPLE_DATA_DIR") &&
       len && len < sizeof(buffer) )
    dataDir = buffer;
#else
  char const *envDir = std::getenv("EXAMPLE_DATA_DIR");
  if (envDir)
    dataDir = envDir;
#endif
  std::string dataFile = dataDir + "/" + DATAFILE;
  std::ifstream ifs(dataFile);
  if (!ifs)
    throw std::runtime_error("Could not open " + dataFile);

  VAR = std::vector<std::vector<double>>(NSHARES, std::vector<double>(NSHARES));

  std::string line;
  int row = 0;
  while (std::getline(ifs, line)) {
    // Strip comments (comments start with a '!')
    std::string::size_type comment = line.find('!');
    if (comment != std::string::npos)
      line = line.substr(0, comment);
    // Remove leading whitespace
    line.erase(line.begin(),
               std::find_if(line.begin(), line.end(), [](unsigned char ch) {
                 return !std::isspace(ch);
               }));
    // skip empty lines
    if (line.size() == 0)
      continue;
    // Now read NSHARES values into the current row in VAR
    std::stringstream s(line);
    for (int i = 0; i < NSHARES; ++i)
      s >> VAR[row][i];
    ++row;
  }
}

FolioQP.cpp
// (c) 2024-2024 Fair Isaac Corporation

/**
 * Modeling a small QP problem to perform portfolio optimization. -- 1. QP:
 * minimize variance 2. MIQP: limited number of assets ---
 */

#include <iostream>
#include <xpress.hpp>

using namespace xpress;
using namespace xpress::objects;
using xpress::objects::utils::scalarProduct;
using xpress::objects::utils::sum;

/** The file from which data for this example is read. */
char const *const DATAFILE = "foliocppqp.dat";
/* Target yield */
int const TARGET = 9;
/* Max. number of different assets */
int const MAXNUM = 4;
/* Number of shares */
int const NSHARES = 10;
/* Number of North-American shares */
int const NNA = 4;
/* Estimated return in investment */
std::vector<double> RET{5, 17, 26, 12, 8, 9, 7, 6, 31, 21};
/* Shares issued in N.-America */
std::vector<int> NA{0, 1, 2, 3};
/* Variance/covariance matrix of estimated returns */
std::vector<std::vector<double>> VAR;

void readData();

void printProblemStatus(XpressProblem const &prob) {
  std::cout << "Problem status:" << std::endl
            << "\tSolve status: " << prob.attributes.getSolveStatus()
            << std::endl
            << "\tSol status: " << prob.attributes.getSolStatus() << std::endl;
}

int main() {
  readData();
  XpressProblem prob;

  // Output all messages.
  prob.callbacks.addMessageCallback(XpressProblem::console);

  /***** First problem: unlimited number of assets *****/

  /**** VARIABLES ****/
  std::vector<Variable> frac =
      prob.addVariables(NSHARES)
          /* Fraction of capital used per share */
          .withName("frac_%d")
          /* Upper bounds on the investment per share */
          .withUB(0.3)
          .toArray();

  /**** CONSTRAINTS ****/
  /* Minimum amount of North-American values */
  prob.addConstraint(sum(NNA, [&](auto i) { return frac[NA[i]]; }) >= 0.5);

  /* Spend all the capital */
  prob.addConstraint(sum(frac) == 1.0);

  /* Target yield */
  prob.addConstraint(scalarProduct(frac, RET) >= TARGET);

  /* Objective: minimize mean variance */
  Expression variance = sum(NSHARES, [&](auto s) {
    return sum(NSHARES, [&](auto t) { return VAR[s][t] * frac[s] * frac[t]; });
  });
  prob.setObjective(variance, ObjSense::Minimize);

  /* Solve */
  prob.optimize();

  /* Solution printing */
  printProblemStatus(prob);
  std::cout << "With a target of " << TARGET << " minimum variance is "
            << prob.attributes.getObjVal() << std::endl;
  auto sollp = prob.getSolution();
  for (int i = 0; i < NSHARES; ++i)
    std::cout << frac[i].getName() << " : " << 100.0 * frac[i].getValue(sollp)
              << "%" << std::endl;

  /***** Second problem: limit total number of assets *****/
  std::vector<Variable> buy = prob.addVariables(NSHARES)
                                  /* Fraction of capital used per share */
                                  .withName("buy_%d")
                                  .withType(ColumnType::Binary)
                                  .toArray();

  /* Limit the total number of assets */
  prob.addConstraint(sum(buy) <= MAXNUM);

  /* Linking the variables */
  /* frac .<= buy */
  prob.addConstraints(NSHARES, [&](auto i) { return frac[i] <= buy[i]; });

  /* Solve */
  prob.optimize();

  /* Solution printing */
  printProblemStatus(prob);
  std::cout << "With a target of " << TARGET << " and at most " << MAXNUM
            << " assets"
            << ", minimum variance is " << prob.attributes.getObjVal()
            << std::endl;
  auto solmip = prob.getSolution();
  for (int i = 0; i < NSHARES; ++i)
    std::cout << frac[i].getName() << " : " << 100.0 * frac[i].getValue(solmip)
              << "%" << std::endl;
  return 0;
}

#include <fstream>
#include <sstream>
#include <cctype>
void readData() {
  std::string dataDir("../../data");
#ifdef _WIN32
  size_t len;
  char buffer[1024];
  if ( !getenv_s(&len, buffer, sizeof(buffer), "EXAMPLE_DATA_DIR") &&
       len && len < sizeof(buffer) )
    dataDir = buffer;
#else
  char const *envDir = std::getenv("EXAMPLE_DATA_DIR");
  if (envDir)
    dataDir = envDir;
#endif
  std::string dataFile = dataDir + "/" + DATAFILE;
  std::ifstream ifs(dataFile);
  if (!ifs)
    throw std::runtime_error("Could not open " + dataFile);

  VAR = std::vector<std::vector<double>>(NSHARES, std::vector<double>(NSHARES));

  std::string line;
  int row = 0;
  while (std::getline(ifs, line)) {
    // Strip comments (comments start with a '!')
    std::string::size_type comment = line.find('!');
    if (comment != std::string::npos)
      line = line.substr(0, comment);
    // Remove leading whitespace
    line.erase(line.begin(),
               std::find_if(line.begin(), line.end(), [](unsigned char ch) {
                 return !std::isspace(ch);
               }));
    // skip empty lines
    if (line.size() == 0)
      continue;
    // Now read NSHARES values into the current row in VAR
    std::stringstream s(line);
    for (int i = 0; i < NSHARES; ++i)
      s >> VAR[row][i];
    ++row;
  }
}

© 2001-2025 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.