Skip to content

Uncapacitated Facility Location Problem (UFLP) Example

Download Notebook


The Uncapacitated Facility Location Problem (UFLP) asks which facilities to open and how to assign customers to minimize the total cost of opening facilities plus transporting goods. Each facility has a fixed opening cost and unlimited capacity.

import getpass
import os

import matplotlib.pyplot as plt
from dotenv import load_dotenv
from luna_quantum.algorithms import SCIP

from luna_usecases.uncapacitated_facility_location_problem import (
    UflpCollection,
    UflpData,
    UflpFormulation,
    UflpInstance,
)

load_dotenv()
if "LUNA_API_KEY" not in os.environ:
    os.environ["LUNA_API_KEY"] = getpass.getpass("Enter your Luna API key: ")

Create Data

Define a small instance with 3 potential warehouse locations and 5 customers. Each facility has a fixed opening cost and there is a per-unit transport cost from each customer to each facility.

data = UflpData.from_cost_matrix(
    facility_costs=[100.0, 150.0, 120.0],
    transport_costs=[
        [10.0, 30.0, 25.0],  # customer 0
        [25.0, 10.0, 30.0],  # customer 1
        [15.0, 25.0, 10.0],  # customer 2
        [20.0, 15.0, 25.0],  # customer 3
        [30.0, 20.0, 15.0],  # customer 4
    ],
)
print(data.to_string())
Uncapacitated Facility Location Problem:
  Facilities: 3
  Customers: 5
  Facility costs: ['100.0', '150.0', '120.0']
  Transport cost range: [10.0, 30.0]

Plot Data

Visualize the problem as a bipartite graph with facilities on the left and customers on the right. If positions are povided, actual positions are plotted

ax = UflpData.generate_random().plot()
plt.show()

png

Create Formulation

Set up binary decision variables for facility opening and customer assignment.

formulation = UflpFormulation()
print(formulation.to_string(data))
Uncapacitated Facility Location Formulation:
  Facilities: 3
  Customers: 5

Decision Variables:
  y_j ∈ {0,1} for j = 0, ..., 2 (facility opening)
  x_ij ∈ {0,1} for i = 0, ..., 4, j = 0, ..., 2 (assignment)
  Total: 18 binary variables

Objective:
  minimize  Σ_j f_j · y_j  +  Σ_i Σ_j c_ij · x_ij

Constraints:
  1. Assignment: Σ_j x_ij = 1 for all i  (5 constraints)
  2. Linking: x_ij ≤ y_j for all i, j  (15 constraints)

Create Instance

Combine data and formulation into a solvable instance.

instance = UflpInstance(data=data, formulation=formulation)
print(instance.to_string())
Data:Uncapacitated Facility Location Problem:
  Facilities: 3
  Customers: 5
  Facility costs: ['100.0', '150.0', '120.0']
  Transport cost range: [10.0, 30.0]
Formulation:Uncapacitated Facility Location Formulation:
  Facilities: 3
  Customers: 5

Decision Variables:
  y_j ∈ {0,1} for j = 0, ..., 2 (facility opening)
  x_ij ∈ {0,1} for i = 0, ..., 4, j = 0, ..., 2 (assignment)
  Total: 18 binary variables

Objective:
  minimize  Σ_j f_j · y_j  +  Σ_i Σ_j c_ij · x_ij

Constraints:
  1. Assignment: Σ_j x_ij = 1 for all i  (5 constraints)
  2. Linking: x_ij ≤ y_j for all i, j  (15 constraints)

Formulate Model

Translate the instance into a mathematical optimization model.

model = instance.formulate()
print(model)
Model: uncapacitated_facility_location_problem<uncapacitated_facility_location_problem>
Minimize
  100 * y_F0 + 150 * y_F1 + 120 * y_F2 + 10 * x_C0_F0 + 30 * x_C0_F1
  + 25 * x_C0_F2 + 25 * x_C1_F0 + 10 * x_C1_F1 + 30 * x_C1_F2 + 15 * x_C2_F0
  + 25 * x_C2_F1 + 10 * x_C2_F2 + 20 * x_C3_F0 + 15 * x_C3_F1 + 25 * x_C3_F2
  + 30 * x_C4_F0 + 20 * x_C4_F1 + 15 * x_C4_F2
Subject To
  assign_C0: x_C0_F0 + x_C0_F1 + x_C0_F2 == 1
  assign_C1: x_C1_F0 + x_C1_F1 + x_C1_F2 == 1
  assign_C2: x_C2_F0 + x_C2_F1 + x_C2_F2 == 1
  assign_C3: x_C3_F0 + x_C3_F1 + x_C3_F2 == 1
  assign_C4: x_C4_F0 + x_C4_F1 + x_C4_F2 == 1
  link_C0_F0: -y_F0 + x_C0_F0 <= 0
  link_C0_F1: -y_F1 + x_C0_F1 <= 0
  link_C0_F2: -y_F2 + x_C0_F2 <= 0
  link_C1_F0: -y_F0 + x_C1_F0 <= 0
  link_C1_F1: -y_F1 + x_C1_F1 <= 0
  link_C1_F2: -y_F2 + x_C1_F2 <= 0
  link_C2_F0: -y_F0 + x_C2_F0 <= 0
  link_C2_F1: -y_F1 + x_C2_F1 <= 0
  link_C2_F2: -y_F2 + x_C2_F2 <= 0
  link_C3_F0: -y_F0 + x_C3_F0 <= 0
  link_C3_F1: -y_F1 + x_C3_F1 <= 0
  link_C3_F2: -y_F2 + x_C3_F2 <= 0
  link_C4_F0: -y_F0 + x_C4_F0 <= 0
  link_C4_F1: -y_F1 + x_C4_F1 <= 0
  link_C4_F2: -y_F2 + x_C4_F2 <= 0
Binary
  y_F0 y_F1 y_F2 x_C0_F0 x_C0_F1 x_C0_F2 x_C1_F0 x_C1_F1 x_C1_F2 x_C2_F0 x_C2_F1
  x_C2_F2 x_C3_F0 x_C3_F1 x_C3_F2 x_C4_F0 x_C4_F1 x_C4_F2

Solve and Interpret

Solve the model with SCIP and interpret the raw result into a use-case-specific solution.

scip = SCIP()
job = scip.run(model)
sol = job.result()
uc_solution = instance.interpret(sol)
print(uc_solution.to_string())
/Users/maximilianjanetschek/PycharmProjects/luna-usecases/.venv/lib/python3.13/site-packages/rich/live.py:260:
UserWarning: install "ipywidgets" for Jupyter support
  warnings.warn('install "ipywidgets" for Jupyter support')








Uncapacitated Facility Location Solution:
  Open facilities: F0
  Assignments: C0 → F0, C1 → F0, C2 → F0, C3 → F0, C4 → F0
  Total cost: 200.00
    Facility cost: 100.00
    Transport cost: 100.00
  Valid: True

Plot Solution

Visualize the optimal solution showing open facilities, closed facilities, and customer assignments.

ax = uc_solution.plot(data)
plt.show()

png

Collections

Generate a benchmark collection of random instances for batch processing.

collection = UflpCollection.from_random(
    min_num_facilities=3,
    max_num_facilities=6,
    num_instances=2,
    seed=42,
)

print(f"Collection: {collection.name}")
print(f"Description: {collection.description}")
print(f"Total instances: {len(collection.instances)}")

# Formulate the first instance
model = collection.instances[0].formulate()
print(f"\nFirst instance model: {model.name}")

Collection: Random UFL 3-6 facilities
Description: Random UFLP instances with 2 per size, customer ratio 2.0
Total instances: 8

First instance model: uncapacitated_facility_location_problem<uncapacitated_facility_location_problem>
clust = UflpCollection.from_clustered(
    min_num_facilities=3,
    max_num_facilities=6,
    cluster_std=0.15,
    seed=42,
)
clust.instances[0].data.plot()

<Axes: title={'center': 'UFL — 3 facilities, 6 customers'}>
png