FICO
FICO Xpress Optimization Examples Repository
FICO Optimization Community FICO Xpress Optimization Home
Back to examples browserPrevious exampleNext example

Purchase - Definition of SOS-2

Description
A model for optimal purchasing with price-breaks featuring a complex MIP model, and formulation options using piecewise linear constraints (PurchasePWL) or SOS-2 (PurchaseSOS2).

purchase_dnet.zip[download all files]

Source Files
By clicking on a file name, a preview is opened at the bottom of this page.
PurchasePWL.cs[download]
PurchasePWL.csproj[download]
PurchaseSOS2.cs[download]
PurchaseSOS2.csproj[download]





PurchasePWL.cs

// (c) 2023-2024 Fair Isaac Corporation

using Optimizer.Objects;
using static Optimizer.Objects.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace XpressExamples
{
    /// <summary>Purchasing problem with price breaks using PieceWise Linear constraints</summary>
    /// <remarks>
    ///   There are three suppliers of a good, and they have quoted various
    ///   prices for various quantities of product.
    ///   We want to buy at least total cost, yet not buy too much from any
    ///   one supplier.
    ///
    ///   Each supplier offers decreasing prices for increased lot size, in the
    ///   form of incremental discounts.
    /// </remarks>
    class PurchasePWL
    {
        static readonly String DATAFILE = (Environment.GetEnvironmentVariable("EXAMPLE_DATA_DIR") != null ? Environment.GetEnvironmentVariable("EXAMPLE_DATA_DIR") : ".") + "/purchase.cdat";

        const int NB = 4;     /* Number of breakpoints */
        static double REQ;              /* Total quantity required */
        static int[] SUPPLIERS;         /* Suppliers */
        static double[] MAXPERC;        /* Maximum percentages for each supplier */
        static double[][] COST;          /* Industry sector of shares */
        static double[][] BREAKP;        /* Breakpoints (quantities at which unit cost changes) */

        public static void Main(string[] args)
        {
            ReadData();                     // Read data from file

            using (XpressProblem prob = new XpressProblem())
            {

                // Create the decision variables
                // Quantity to purchase from supplier s
                Variable[] buy = prob.AddVariables(SUPPLIERS.Length)
                        .WithUB(s => MAXPERC[s] * REQ / 100.0)
                        .WithName("buy {0}")
                        .ToArray();

                // Cost incurred from supplier s
                Variable[] cost = prob.AddVariables(SUPPLIERS.Length)
                        .WithName("cost {0}")
                        .ToArray();

                // The minimum quantity that must be bought
                prob.AddConstraint(Sum(buy) >= REQ);

                // Function to calculate cost at breakpoints
                double[][] COSTBREAK = new double[SUPPLIERS.Length][];
                for (int s = 0; s < SUPPLIERS.Length; s++)
                {
                    COSTBREAK[s] = new double[NB];
                    COSTBREAK[s][0] = 0;
                    for (int b = 1; b < NB; b++)
                        COSTBREAK[s][b] = COSTBREAK[s][b - 1] + COST[s][b] * (BREAKP[s][b] - BREAKP[s][b - 1]);
                }

                // Define relation between bought quantities and price paid per supplier
                for (int s = 0; s < SUPPLIERS.Length; s++)
                {
                    prob.AddConstraint(cost[s].PwlOf(buy[s], BREAKP[s], COSTBREAK[s]));
                }

                // Objective: total return
                prob.SetObjective(Sum(cost));

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

                Console.WriteLine("Problem status: " + prob.MIPStatus);
                if (prob.MIPStatus != Optimizer.MIPStatus.Solution &&
                        prob.MIPStatus != Optimizer.MIPStatus.Optimal)
                    throw new Exception("optimization failed with status " + prob.MIPStatus);

                // Solution printing
                Console.WriteLine("Total cost: " + prob.ObjVal);
                double[] sol = prob.GetSolution();
                for (int s = 0; s < SUPPLIERS.Length; s++)
                    if (buy[s].GetValue(sol) > 0.5)
                        Console.WriteLine("Supp. " + SUPPLIERS[s] + ": buy:" + buy[s].GetValue(sol) + ": cost:" + cost[s].GetValue(sol));
            }
        }

        /// <summary>Read a data vector</summary>
        /// <typeparam name="T">Data type.</typeparam>
        /// <param name="tokens">Token provider</param>
        /// <param name="makeData">Function to turn a <c>string</c> token into an instance of <c>T</c>.</param>
        /// <returns>The next vector read from <c>tokens</c>.</returns>
        private static T[] ReadVector<T>(IEnumerator<string> tokens, Func<string, T> makeData)
        {
            List<T> data = new List<T>();
            while (tokens.MoveNext())
            {
                string token = tokens.Current;
                if (token.Equals(";")) // Semicolon terminates vector
                    break;
                data.Add(makeData(token));
            }
            return data.ToArray();
        }
        /// <summary>Read a table of doubles.</summary>
        /// <remarks>
        /// Returns an <c>nrow</c> by <c>ncol</c> table of doubles.
        /// </remarks>
        /// <param name="tokens">Token provider.</param>
        /// <param name="nrow">Number of rows.</param>
        /// <param name="ncol">Number of columns.</param>
        /// <returns><c>nrow</c> by <c>ncol</c> double array.</returns>
        private static double[][] ReadTable(IEnumerator<string> tokens, int nrow, int ncol)
        {
            double[][] table = new double[nrow][];

            for (int r = 0; r < nrow; ++r)
            {
                table[r] = new double[ncol];
                table[r] = ReadVector(tokens, s => Double.Parse(s));
            }
            return table;
        }

        /// <summary>Fill the static data fields.</summary>
        private static void ReadData()
        {
            // Split the file content into tokens
            IEnumerator<string> tokens = File.ReadAllLines(DATAFILE)
                .SelectMany(s => Regex.Split(s, "\\s+")) // Split tokens at whitespace
                .SelectMany(s => (s.Length > 1 && s.EndsWith(";")) ? new string[] { s.Substring(0, s.Length - 1), ";" } : new string[] { s }) // Split comma into separate token
                .Where(s => s.Length > 0) // filter empty strings
                .GetEnumerator();
            while (tokens.MoveNext())
            {
                string token = tokens.Current;
                if (token.Equals("SUPPLIERS:"))
                {
                    SUPPLIERS = ReadVector(tokens, s => Int32.Parse(s));
                }
                else if (token.Equals("MAXPERC:"))
                {
                    MAXPERC = ReadVector(tokens, s => Double.Parse(s));
                }
                else if (token.Equals("REQ:"))
                {
                    tokens.MoveNext();
                    REQ = Double.Parse(tokens.Current);
                }
                else if (token.Equals("BREAKP:"))
                {
                    BREAKP = ReadTable(tokens, SUPPLIERS.Length, NB);
                }
                else if (token.Equals("COST:"))
                {
                    COST = ReadTable(tokens, SUPPLIERS.Length, NB);
                }
            }
        }
    }
}

Back to examples browserPrevious exampleNext example