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
- Add set decision variables to model the sites assigned to each facility
- Use lambda expressions to compute the total demand and total cost for each facility
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\pythonpython capacitated_facility_location.py instances\cap61
- Execution (Linux)
-
export PYTHONPATH=/opt/hexaly_13_0/bin/pythonpython 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.libcapacitated_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.dllCapacitatedFacilityLocation 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.jarjava -cp %HX_HOME%\bin\hexaly.jar;. CapacitatedFacilityLocation instances\cap61
- Compilation / Execution (Linux)
-
javac CapacitatedFacilityLocation.java -cp /opt/hexaly_13_0/bin/hexaly.jarjava -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);
}
}
}