Capacitated Facility Location (CFLP) Example
The Capacitated Facility Location Problem decides which facilities to open (each with a fixed cost and a finite capacity) and assigns every customer to exactly one open facility, so that no facility's capacity is exceeded and total opening + assignment cost is minimized. It is NP-hard and arises in supply-chain and network design.
import getpass
import os
import numpy as np
from dotenv import load_dotenv
from luna_quantum.algorithms import SCIP
from luna_usecases.capacitated_facility_location_problem import (
CflpCollection,
CflpData,
CflpFormulation,
CflpInstance,
)
load_dotenv()
if "LUNA_API_KEY" not in os.environ:
os.environ["LUNA_API_KEY"] = getpass.getpass("Enter your Luna API key: ")
Create Data
Build an instance from explicit cost, demand and capacity arrays using the from_costs factory.
data = CflpData.from_costs(
opening_costs=[100.0, 150.0],
assignment_costs=np.array(
[
[10.0, 20.0],
[25.0, 10.0],
[15.0, 25.0],
[20.0, 15.0],
]
),
demands=[1.0, 1.0, 1.0, 1.0],
capacities=[3.0, 3.0],
)
print(data.to_string())
Capacitated Facility Location Problem:
Facilities: 2
Customers: 4
Opening costs: ['100.0', '150.0']
Capacities: ['3.0', '3.0']
Demands: ['1.0', '1.0', '1.0', '1.0']
Assignment cost range: [10.0, 25.0]
Plot Data
Visualize the facilities and customers as a bipartite graph.
Create Formulation
Minimize opening + assignment cost subject to single-source assignment and facility capacities.
Capacitated Facility Location Formulation:
Facilities: 2
Customers: 4
Decision Variables:
y_j in {0,1} for j = 0, ..., 1 (facility opening)
x_ij in {0,1} for i = 0, ..., 3, j = 0, ..., 1 (assignment)
Total: 10 binary variables
Objective:
minimize sum_j f_j * y_j + sum_i sum_j c_ij * x_ij
Constraints:
1. Assignment: sum_j x_ij == 1 for all i (4 constraints)
2. Capacity: sum_i d_i * x_ij <= s_j * y_j for all j (2 constraints)
Create Instance
Combine data and formulation into a solvable instance.
Data:Capacitated Facility Location Problem:
Facilities: 2
Customers: 4
Opening costs: ['100.0', '150.0']
Capacities: ['3.0', '3.0']
Demands: ['1.0', '1.0', '1.0', '1.0']
Assignment cost range: [10.0, 25.0]
Formulation:Capacitated Facility Location Formulation:
Facilities: 2
Customers: 4
Decision Variables:
y_j in {0,1} for j = 0, ..., 1 (facility opening)
x_ij in {0,1} for i = 0, ..., 3, j = 0, ..., 1 (assignment)
Total: 10 binary variables
Objective:
minimize sum_j f_j * y_j + sum_i sum_j c_ij * x_ij
Constraints:
1. Assignment: sum_j x_ij == 1 for all i (4 constraints)
2. Capacity: sum_i d_i * x_ij <= s_j * y_j for all j (2 constraints)
Formulate Model
Translate the instance into a mixed-integer optimization model.
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/maxi/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')
2026-06-23 17:36:16 INFO Sleeping for 5.0 seconds. Waiting and checking a function in a loop.
2026-06-23 17:36:22 INFO Sleeping for 10.0 seconds. Waiting and checking a function in a loop.
2026-06-23 17:36:32 INFO Sleeping for 15.0 seconds. Waiting and checking a function in a loop.
Capacitated Facility Location Solution:
Open facilities: F0, F1
Assignments: C0 -> F0, C1 -> F1, C2 -> F0, C3 -> F1
Total cost: 300.00
Opening cost: 250.00
Assignment cost: 50.00
Valid: True
Plot Solution
Visualize the open facilities and customer assignments.
Collections
Generate a benchmark collection of random instances for batch processing.
collection = CflpCollection.from_random(min_facilities=2, max_facilities=3, num_instances=2, seed=42)
model = collection.instances[0].formulate()
print(model)
Model: capacitated_facility_location_problem<s42_f2_i0>
Minimize
81.19393091638298 * y_F0 + 97.64109328607654 * y_F1
+ 43.27993355336387 * x_C0_F0 + 46.653221594274626 * x_C0_F1
+ 43.355880784982986 * x_C1_F0 + 27.723393779338352 * x_C1_F1
+ 21.217338245119564 * x_C2_F0 + 13.705376561946622 * x_C2_F1
+ 45.275961907297884 * x_C3_F0 + 50 * x_C3_F1
Subject To
assign_C0: x_C0_F0 + x_C0_F1 == 1
assign_C1: x_C1_F0 + x_C1_F1 == 1
assign_C2: x_C2_F0 + x_C2_F1 == 1
assign_C3: x_C3_F0 + x_C3_F1 == 1
capacity_F0: -10.333942708091739 * y_F0 + 1.5550751146990365 * x_C0_F0
+ 4.068156349659279 * x_C1_F0 + 3.434506070510315 * x_C2_F0
+ 1.2762051732231074 * x_C3_F0 <= 0
capacity_F1: -10.333942708091739 * y_F1 + 1.5550751146990365 * x_C0_F1
+ 4.068156349659279 * x_C1_F1 + 3.434506070510315 * x_C2_F1
+ 1.2762051732231074 * x_C3_F1 <= 0
Binary
y_F0 y_F1 x_C0_F0 x_C0_F1 x_C1_F0 x_C1_F1 x_C2_F0 x_C2_F1 x_C3_F0 x_C3_F1