Capacitated Facility Location Problem (CFLP)

Problem

In the Capacitated Facility Location Problem (CFLP), a number of sites with known demand must be assigned to available facilities. The total demand of the sites assigned to each facility must not exceed its capacity. Each facility has a fixed opening price that must be paid if it serves at least one site. There are also allocation costs between the facilities and the sites they serve. The objective is to minimize the total cost. This problem is also known as the Capacitated Warehouse Problem or Capacitated P-median Problem.

Principles learned

Data

The data files provided come from the OR-LIB. The format is as follows:

  • First line: the number of potential facilities and the number of sites
  • For each facility, its capacity and its opening price
  • For each site, its demand
  • For each facility and each site, the related allocation cost.

Program

The Hexaly model for the Capacitated Facility Location Problem (CFLP) uses set decision variables to represent the sites assigned to each facility. Thanks to the ‘partition’ operator, we ensure that each site is assigned to exactly one facility.

We can access the demand for each site in a sequence using the ‘at’ operator on the demand array. The total demand served by each facility is computed with a lambda function to apply the ‘sum’ operator over all associated sites. Note that the number of terms in this sum varies during the search, along with the size of the set. We can then constrain this quantity to be lower than the facility’s capacity.

Similarly, we compute the allocation cost associated with each facility, as the sum of allocation prices over all the sites it serves. Using the ‘count’ operator, we check whether each facility serves at least one site. If so, the facility’s opening cost must also be paid.

The objective function is the sum of opening and allocation costs over all facilities.

Execution
hexaly capacitated_facility_location.hxm inFileName=instances/cap61 [hxTimeLimit=] [solFileName=]
use io;

/* Read instance data */
function input() {
    usage = "Usage: hexaly capacitated_facility_location.hxm "
            + "inFileName=inputFile [solFileName=outputFile] [hxTimeLimit=timeLimit]";

    if (inFileName == nil) throw usage;
    local inFile = io.openRead(inFileName);
    nbMaxFacilities = inFile.readInt();
    nbSites = inFile.readInt();

    for [f in 0...nbMaxFacilities] {
        // List of facilities capacities
        capacity[f] = inFile.readDouble();
        // List of fixed costs induced by the facilities opening
        openingPrice[f] = inFile.readDouble();
    }

    // Demand of each site
    demand[s in 0...nbSites] = inFile.readDouble();

    // Allocation price between sites and facilities
    allocationPrice[f in 0...nbMaxFacilities][s in 0...nbSites] = inFile.readDouble();
}

/* Declare the optimization model */
function model() {
    // Facilities are represented by the set of sites they provide
    facilityAssignments[f in 0...nbMaxFacilities] <- set(nbSites);

    // Each site is covered by exactly one facility
    constraint partition[f in 0...nbMaxFacilities](facilityAssignments[f]);

    for [f in 0...nbMaxFacilities] {
        local facility <- facilityAssignments[f];
        local size <- count(facility);

        // Capacity constraint
        constraint sum(facility, i => demand[i]) <= capacity[f];

        // Cost (allocation price + opening price)
        cost[f] <- sum(facility, i => allocationPrice[f][i]) + openingPrice[f] * (size > 0);
    }

    // Objective : minimize total cost
    totalCost <- sum[f in 0...nbMaxFacilities](cost[f]);
    minimize totalCost;
}

/* Parametrize the solver */
function param() {
    if (hxTimeLimit == nil) hxTimeLimit = 20;
}

/* Write the solution in a file with the following format:
 * - value of the objective
 * - indices of the open facilities followed by all the sites they provide */
function output() {
    if (solFileName == nil) return;
    local outfile = io.openWrite(solFileName);
    outfile.println(totalCost.value);
    for [f in 0...nbMaxFacilities : cost[f].value > 0] {
        outfile.println(f);
        for [s in facilityAssignments[f].value]
            outfile.print(s, " ");
        outfile.println();
    }
}
Execution (Windows)
set PYTHONPATH=%HX_HOME%\bin\python
python capacitated_facility_location.py instances\cap61
Execution (Linux)
export PYTHONPATH=/opt/hexaly_13_0/bin/python
python capacitated_facility_location.py instances/cap61
import hexaly.optimizer
import sys


def main(instanceFile, strTimeLimit, solFile):

    #
    # Read instance data
    #
    nb_max_facilities, nb_sites, capacity_data, opening_price_data, \
        demand_data, allocation_price_data = read_data(instanceFile)

    with hexaly.optimizer.HexalyOptimizer() as optimizer:
        #
        # Declare the optimization model
        #
        model = optimizer.model

        # Facilities are represented by the set of sites they provide
        facility_assignments = [model.set(nb_sites) for _ in range(nb_max_facilities)]

        # Each site is covered by exactly one facility
        model.constraint(model.partition(facility_assignments))

        # Converting demand and allocationPrice into Hexaly array
        demand = model.array(demand_data)
        allocation_price = model.array(allocation_price_data)

        cost = [None] * nb_max_facilities
        for f in range(nb_max_facilities):
            facility = facility_assignments[f]
            size = model.count(facility)

            # Capacity constraint
            demand_lambda = model.lambda_function(lambda i: demand[i])
            model.constraint(model.sum(facility, demand_lambda) <= capacity_data[f])

            # Cost (allocation price + opening price)
            costSelector = model.lambda_function(lambda i: model.at(allocation_price, f, i))
            cost[f] = model.sum(facility, costSelector) + opening_price_data[f] * (size > 0)

        # Objective : minimize total cost
        totalCost = model.sum(cost)
        model.minimize(totalCost)

        model.close()

        # Parameterize the optimizer
        optimizer.param.time_limit = int(strTimeLimit)

        optimizer.solve()

        #
        # Write the solution in a file with the following format:
        # - value of the objective
        # - indices of the open facilities followed by all the sites they provide
        #
        if solFile:
            with open(solFile, 'w') as outputFile:
                outputFile.write("%d" % totalCost.value)
                for f in range(nb_max_facilities):
                    if cost[f].value > 0:
                        outputFile.write("%d\n" % f)
                        for site in facility_assignments[f].value:
                            outputFile.write("%d " % site)
                        outputFile.write("\n")


def read_elem(filename):
    with open(filename) as f:
        return [str(elem) for elem in f.read().split()]


def read_data(filename):
    file_it = iter(read_elem(filename))

    nb_max_facilities = int(next(file_it))
    nb_sites = int(next(file_it))

    capacity_data = []
    opening_price_data = []
    demand_data = []
    allocation_price_data = []

    for f in range(nb_max_facilities):
        # List of facilities capacities
        capacity_data.append(float(next(file_it)))
        # List of fixed costs induced by the facilities opening
        opening_price_data.append(float(next(file_it)))
        allocation_price_data.append([])

    # Demand of each site
    for s in range(nb_sites):
        demand_data.append(float(next(file_it)))

    # Allocation price between sites and facilities
    for f in range(nb_max_facilities):
        for s in range(nb_sites):
            allocation_price_data[f].append(float(next(file_it)))

    return nb_max_facilities, nb_sites, capacity_data, opening_price_data, \
        demand_data, allocation_price_data


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python capacitated_facility_location.py input_file \
            [output_file] [time_limit]")
        sys.exit(1)

    instanceFile = sys.argv[1]
    solFile = sys.argv[2] if len(sys.argv) > 2 else None
    strTimeLimit = sys.argv[3] if len(sys.argv) > 3 else "20"

    main(instanceFile, strTimeLimit, solFile)
Compilation / Execution (Windows)
cl /EHsc capacitated_facility_location.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly130.lib
capacitated_facility_location instances\cap61
Compilation / Execution (Linux)
g++ capacitated_facility_location.cpp -I/opt/hexaly_13_0/include -lhexaly130 -lpthread -o capacitated_facility_location 
./capacitated_facility_location instances/cap61
#include "optimizer/hexalyoptimizer.h"
#include <fstream>
#include <iostream>
#include <string.h>
#include <vector>

using namespace hexaly;
using namespace std;

class CapacitatedFacilityLocation {
public:
    // Data from the problem
    int nbMaxFacilities;
    int nbSites;
    vector<double> capacityData;
    vector<double> openingPriceData;
    vector<double> demandData;
    vector<vector<double>> allocationPriceData;

    // Hexaly Optimizer
    HexalyOptimizer optimizer;

    // Variables
    vector<HxExpression> facilityAssignments;

    // Objective
    vector<HxExpression> cost;
    HxExpression totalCost;

    void solve(int limit) {
        // Declare the optimization model
        HxModel model = optimizer.getModel();

        // Table of sets representing the sites provided by each facility
        facilityAssignments.resize(nbMaxFacilities);
        for (int f = 0; f < nbMaxFacilities; ++f) {
            facilityAssignments[f] = model.setVar(nbSites);
        }

        // Each site is covered by exactly one facility
        model.constraint(model.partition(facilityAssignments.begin(), facilityAssignments.end()));

        // Converting demand and allocationPrice into HexalyOptimizer array
        HxExpression demand = model.array(demandData.begin(), demandData.end());
        HxExpression allocationPrice = model.array();
        for (int f = 0; f < nbMaxFacilities; ++f) {
            allocationPrice.addOperand(model.array(allocationPriceData[f].begin(), allocationPriceData[f].end()));
        }

        cost.resize(nbMaxFacilities);
        for (int f = 0; f < nbMaxFacilities; ++f) {
            HxExpression facility = facilityAssignments[f];
            HxExpression size = model.count(facility);

            // Capacity constraint
            HxExpression demandLambda = model.createLambdaFunction([&](HxExpression i) { return demand[i]; });
            model.constraint(model.sum(facility, demandLambda) <= capacityData[f]);

            // Cost (allocation price + opening price)
            HxExpression costLambda =
                model.createLambdaFunction([&](HxExpression i) { return model.at(allocationPrice, f, i); });
            cost[f] = model.sum(facility, costLambda) + (size > 0) * openingPriceData[f];
        }

        // Objective : minimize total cost
        totalCost = model.sum(cost.begin(), cost.end());
        model.minimize(totalCost);
        model.close();

        // Parametrize the optimizer
        optimizer.getParam().setTimeLimit(limit);

        optimizer.solve();
    }

    /* Read instance data */
    void readInstance(const string& fileName) {
        ifstream infile;
        infile.exceptions(ifstream::failbit | ifstream::badbit);
        infile.open(fileName.c_str());

        infile >> nbMaxFacilities;
        infile >> nbSites;
        capacityData.resize(nbMaxFacilities);
        openingPriceData.resize(nbMaxFacilities);
        demandData.resize(nbSites);
        allocationPriceData.resize(nbMaxFacilities, vector<double>(nbSites));

        for (int f = 0; f < nbMaxFacilities; ++f) {
            // List of facilities capacities
            infile >> capacityData[f];
            // List of fixed costs induced by the facilities opening
            infile >> openingPriceData[f];
        }

        // Demand of each site
        for (int s = 0; s < nbSites; ++s) {
            infile >> demandData[s];
        }

        // Allocation price between sites and facilities
        for (int f = 0; f < nbMaxFacilities; ++f) {
            for (int s = 0; s < nbSites; ++s) {
                infile >> allocationPriceData[f][s];
            }
        }

        infile.close();
    }

    /* Write the solution in a file with the following format:
     * - value of the objective
     * - indices of the open facilities followed by all the sites they provide */
    void writeSolution(const string& fileName) {
        ofstream outfile;
        outfile.exceptions(ofstream::failbit | ofstream::badbit);
        outfile.open(fileName.c_str());
        outfile << totalCost.getDoubleValue() << endl;
        for (int f = 0; f < nbMaxFacilities; ++f) {
            if (cost[f].getDoubleValue() > 0) {
                outfile << f << endl;
                HxCollection sites_assigned = facilityAssignments[f].getCollectionValue();
                for (hxint s = 0; s < sites_assigned.count(); ++s) {
                    outfile << sites_assigned[s] << " ";
                }
                outfile << endl;
            }
        }
    }
};

int main(int argc, char** argv) {
    if (argc < 2) {
        cerr << "Usage: capacitated_facility_location inputFile [outputfile] [timeLimit]" << endl;
        return 1;
    }
    const char* instanceFile = argv[1];
    const char* solFile = argc > 2 ? argv[2] : NULL;
    const char* strTimeLimit = argc > 3 ? argv[3] : "20";
    try {
        CapacitatedFacilityLocation model;
        model.readInstance(instanceFile);
        model.solve(atoi(strTimeLimit));
        if (solFile != NULL)
            model.writeSolution(solFile);
    } catch (const exception& e) {
        cerr << "An error occured " << e.what() << endl;
        return -1;
    }
}
Compilation / Execution (Windows)
copy %HX_HOME%\bin\Hexaly.NET.dll .
csc CapacitatedFacilityLocation.cs /reference:Hexaly.NET.dll
CapacitatedFacilityLocation instances\cap61
using System;
using System.IO;
using System.Collections.Generic;
using Hexaly.Optimizer;
using System.Globalization;

public class CapacitatedFacilityLocation : IDisposable
{
    // Data from the problem
    int nbMaxFacilities;
    int nbSites;
    double[] capacityData;
    double[] openingPriceData;
    double[] demandData;
    double[,] allocationPriceData;

    // Hexaly Optimizer
    HexalyOptimizer optimizer = new HexalyOptimizer();

    // Variables
    HxExpression[] facilityAssignments;

    // Objective
    HxExpression[] cost;
    HxExpression totalCost;

    private string[] SplitInput(StreamReader input)
    {
        string line = input.ReadLine();
        if (line == null)
            return new string[0];
        return line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
    }

    void ReadInstance(string fileName)
    {
        using (StreamReader input = new StreamReader(fileName))
        {
            string[] splitted;
            splitted = SplitInput(input);
            nbMaxFacilities = int.Parse(splitted[0]);
            nbSites = int.Parse(splitted[1]);

            capacityData = new double[nbMaxFacilities];
            openingPriceData = new double[nbMaxFacilities];
            demandData = new double[nbSites];
            allocationPriceData = new double[nbMaxFacilities, nbSites];

            for (int f = 0; f < nbMaxFacilities; ++f)
            {
                splitted = SplitInput(input);
                // List of facilities capacities
                capacityData[f] = double.Parse(splitted[0], CultureInfo.InvariantCulture);
                // List of fixed costs induced by the facilities opening
                openingPriceData[f] = double.Parse(splitted[1], CultureInfo.InvariantCulture);
            }

            // Demand of each site
            splitted = SplitInput(input);
            for (int s = 0; s < nbSites; ++s)
                demandData[s] = double.Parse(splitted[s], CultureInfo.InvariantCulture);

            // Allocation price between sites and facilities
            for (int f = 0; f < nbMaxFacilities; ++f)
            {
                splitted = SplitInput(input);
                for (int s = 0; s < nbSites; ++s)
                    allocationPriceData[f, s] = double.Parse(splitted[s], CultureInfo.InvariantCulture);
            }
        }
    }

    public void Dispose()
    {
        if (optimizer != null)
            optimizer.Dispose();
    }

    void Solve(int limit)
    {
        // Declare the optimization model
        HxModel model = optimizer.GetModel();

        // Facilities are represented by the sets of sites they provide
        facilityAssignments = new HxExpression[nbMaxFacilities];
        for (int f = 0; f < nbMaxFacilities; ++f)
            facilityAssignments[f] = model.Set(nbSites);

        // Each site is covered by exactly one facility
        model.Constraint(model.Partition(facilityAssignments));

        // Converting demand and allocationPrice into HexalyOptimizer array
        HxExpression demand = model.Array(demandData);
        HxExpression allocationPrice = model.Array(allocationPriceData);

        cost = new HxExpression[nbMaxFacilities];
        for (int f = 0; f < nbMaxFacilities; ++f)
        {
            HxExpression facility = facilityAssignments[f];
            HxExpression size = model.Count(facility);

            // Capacity constraint
            HxExpression demandLambda = model.LambdaFunction(i => demand[i]);
            model.Constraint(model.Sum(facility, demandLambda) <= capacityData[f]);

            // Cost (allocation price + opening price)
            HxExpression costLambda = model.LambdaFunction(i => allocationPrice[f, i]);
            cost[f] = model.Sum(facility, costLambda) + model.If(size > 0, openingPriceData[f], 0);
        }

        // Objective : minimizing total cost
        totalCost = model.Sum(cost);
        model.Minimize(totalCost);

        model.Close();

        // Parameterize the optimizer
        optimizer.GetParam().SetTimeLimit(limit);

        optimizer.Solve();
    }

    // Write the solution in a file with the following format:
    //  - value of the objective
    //  - indices of the open facilities followed by all the sites they provide
    void WriteSolution(string fileName)
    {
        using (StreamWriter output = new StreamWriter(fileName))
        {
            output.WriteLine(totalCost.GetDoubleValue());
            for (int f = 0; f < nbMaxFacilities; ++f)
            {
                if (cost[f].GetDoubleValue() > 0)
                {
                    output.WriteLine(f);
                    HxCollection assigned_sites = facilityAssignments[f].GetCollectionValue();
                    for (int s = 0; s < assigned_sites.Count(); ++s)
                        output.Write(assigned_sites[s] + " ");
                    output.WriteLine();
                }
            }
        }
    }

    public static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine("Usage: CapacitatedFacilityLocation instanceFile [outputFile] [timeLimit]");
            System.Environment.Exit(1);
        }
        
        string instanceFile = args[0];
        string outputFile = args.Length > 1 ? args[1] : null;
        string strTimeLimit = args.Length > 2 ? args[2] : "20";

        using (CapacitatedFacilityLocation model = new CapacitatedFacilityLocation())
        {
            model.ReadInstance(instanceFile);
            model.Solve(int.Parse(strTimeLimit));
            if (outputFile != null)
                model.WriteSolution(outputFile);
        }
    }
}
Compilation / Execution (Windows)
javac CapacitatedFacilityLocation.java -cp %HX_HOME%\bin\hexaly.jar
java -cp %HX_HOME%\bin\hexaly.jar;. CapacitatedFacilityLocation instances\cap61
Compilation / Execution (Linux)
javac CapacitatedFacilityLocation.java -cp /opt/hexaly_13_0/bin/hexaly.jar
java -cp /opt/hexaly_13_0/bin/hexaly.jar:. CapacitatedFacilityLocation instances/cap61
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;

public class CapacitatedFacilityLocation {

    // Data from the problem
    private int nbMaxFacilities;
    private int nbSites;
    private double[] capacityData;
    private double[] openingPriceData;
    private double[] demandData;
    private double[][] allocationPriceData;

    // Hexaly Optimizer
    private final HexalyOptimizer optimizer;

    // Variables
    private HxExpression[] facilityAssignments;

    // Objective
    private HxExpression[] cost;
    private HxExpression totalCost;

    private CapacitatedFacilityLocation(HexalyOptimizer optimizer) {
        this.optimizer = optimizer;
    }

    /* Read instance data */
    private void readInstance(String fileName) throws IOException {
        try (Scanner input = new Scanner(new File(fileName))) {
            nbMaxFacilities = input.nextInt();
            nbSites = input.nextInt();
            capacityData = new double[nbMaxFacilities];
            openingPriceData = new double[nbMaxFacilities];
            demandData = new double[nbSites];
            allocationPriceData = new double[nbMaxFacilities][nbSites];

            for (int f = 0; f < nbMaxFacilities; ++f) {
                // List of facilities capacities
                capacityData[f] = input.nextDouble();
                // List of fixed costs induced by the facilities opening
                openingPriceData[f] = input.nextDouble();
            }

            // Demand of each site
            for (int s = 0; s < nbSites; ++s) {
                demandData[s] = input.nextDouble();
            }

            // Allocation price between sites and facilities
            for (int f = 0; f < nbMaxFacilities; ++f) {
                for (int s = 0; s < nbSites; ++s) {
                    allocationPriceData[f][s] = input.nextDouble();
                }
            }
        }
    }

    private void solve(int limit) {
        // Declare the optimization model
        HxModel model = optimizer.getModel();

        // Facilities are represented by the sets of sites they provide
        facilityAssignments = new HxExpression[nbMaxFacilities];
        for (int f = 0; f < nbMaxFacilities; ++f) {
            facilityAssignments[f] = model.setVar(nbSites);
        }

        // Each site is covered by exactly one facility
        model.constraint(model.partition(facilityAssignments));

        // Converting demand and allocationPrice into HexalyOptimizer array
        HxExpression demand = model.array(demandData);
        HxExpression allocationPrice = model.array(allocationPriceData);

        cost = new HxExpression[nbMaxFacilities];
        for (int f = 0; f < nbMaxFacilities; ++f) {
            HxExpression fExpr = model.createConstant(f);

            HxExpression facility = facilityAssignments[f];
            HxExpression size = model.count(facility);

            // Capacity constraint
            HxExpression demandLambda = model.lambdaFunction(i -> model.at(demand, i));
            model.constraint(model.leq(model.sum(facility, demandLambda), capacityData[f]));

            // Cost (allocation price + opening price)
            HxExpression costLambda = model.lambdaFunction(i -> model.at(allocationPrice, fExpr, i));
            cost[f] = model.sum(model.sum(facility, costLambda), model.iif(model.gt(size, 0), openingPriceData[f], 0));
        }

        // Objective : minimize total cost
        totalCost = model.sum(cost);
        model.minimize(totalCost);

        model.close();

        // Parameterize the optimizer
        optimizer.getParam().setTimeLimit(limit);

        optimizer.solve();
    }

    /*
     * Write the solution in a file with the following format:
     * - value of the objective
     * - indices of the open facilities followed by all the sites they provide
     */
    private void writeSolution(String file_name) throws IOException {
        try (PrintWriter output = new PrintWriter(file_name)) {
            output.println(totalCost.getDoubleValue());
            for (int f = 0; f < nbMaxFacilities; ++f) {
                if (cost[f].getDoubleValue() > 0) {
                    output.println(f);
                    HxCollection sites_assigned = facilityAssignments[f].getCollectionValue();
                    for (int s = 0; s < sites_assigned.count(); ++s) {
                        output.print(sites_assigned.get(s) + " ");
                    }
                    output.println();
                }
            }
        }
    }

    public static void main(String[] args) {
        try (HexalyOptimizer optimizer = new HexalyOptimizer()) {
            String instanceFile = args[0];
            String outputFile = args.length > 1 ? args[1] : null;
            String strTimeLimit = args.length > 2 ? args[2] : "20";

            CapacitatedFacilityLocation model = new CapacitatedFacilityLocation(optimizer);
            model.readInstance(instanceFile);
            model.solve(Integer.parseInt(strTimeLimit));
            if (outputFile != null) {
                model.writeSolution(outputFile);
            }
        } catch (Exception ex) {
            System.err.println(ex);
            ex.printStackTrace();
            System.exit(1);
        }
    }
}