Skip to content

Capacitated Facility Location (CFLP) Example

Download Notebook


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.

data.plot()

<Axes: title={'center': 'CFL — 2 facilities, 4 customers'}>
png

Create Formulation

Minimize opening + assignment cost subject to single-source assignment and facility capacities.

formulation = CflpFormulation()
print(formulation.to_string(data))
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.

instance = CflpInstance(data=data, formulation=formulation)
print(instance.to_string())
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.

model = instance.formulate()

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.

uc_solution.plot(data)

<Axes: title={'center': 'CFL Solution — cost: 300.0'}>
png

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