Your First Use Case
Learn how to create a custom optimization use case from scratch by following this guide.
Overview
Every use case follows a consistent 6-file pattern. You'll create a directory for your use case and add each file using the templates below. Replace Knapsack and knapsack with your own use case name throughout.
Naming Convention
Your use case name must be:
- Lowercase
- Use underscores for spaces (snake_case)
- Start with a letter or underscore
- Contain only letters, numbers, and underscores
Example: For a "Knapsack" problem, use knapsack as the directory name and Knapsack as the class prefix.
Directory Structure
Create the following structure in your project:
src/luna_usecases/knapsack/
├── __init__.py
├── data.py
├── solution.py
├── formulation.py
├── instance.py
└── collection.py
File Templates
Copy each template below into the corresponding file, replacing Knapsack/knapsack with your use case name.
__init__.py - Public Exports
"""Knapsack use case implementation."""
from luna_usecases.knapsack.collection import KnapsackCollection
from luna_usecases.knapsack.data import KnapsackData
from luna_usecases.knapsack.formulation import KnapsackFormulation
from luna_usecases.knapsack.instance import KnapsackInstance
from luna_usecases.knapsack.solution import KnapsackSolution
__all__ = [
"KnapsackCollection",
"KnapsackData",
"KnapsackFormulation",
"KnapsackInstance",
"KnapsackSolution",
]
data.py - Problem Data Model
This file defines the input data for your optimization problem.
"""Data model for Knapsack use case."""
from typing import Literal
import numpy as np
from luna_usecases.abstract import Registry, UcData
@Registry.add
class KnapsackData(UcData):
"""Data for the Knapsack use case.
Attributes
----------
name : Literal["knapsack"]
Identifier for this data type.
# TODO: Add your data field descriptions here
# Example:
# n_items : int
# Number of items in the problem.
# values : list[float]
# Values associated with each item.
"""
name: Literal["knapsack"] = "knapsack"
# TODO: Add your data fields here
# Example:
# n_items: int
# values: list[float]
def print(self) -> str:
"""Print the data.
Returns
-------
str
String representation of the data.
"""
lines = [
"Knapsack Data:",
f" name: {self.name}",
# TODO: Add custom printing logic here
]
return "\n".join(lines)
@staticmethod
def generate_random(
size: int = 10,
seed: int | None = None,
) -> "KnapsackData":
"""Generate a random instance.
Parameters
----------
size : int, optional
Problem size parameter, by default 10.
seed : int | None, optional
Random seed for reproducibility, by default None.
Returns
-------
KnapsackData
A randomly generated data instance.
Examples
--------
>>> data = KnapsackData.generate_random(size=20, seed=42)
"""
if seed is not None:
np.random.seed(seed)
# TODO: Implement random generation logic
# Example:
# values = np.random.rand(size)
# return KnapsackData(n_items=size, values=values.tolist())
raise NotImplementedError("Random generation not yet implemented")
solution.py - Solution Structure
Defines what a solution looks like for your problem.
"""Solution model for Knapsack use case."""
from typing import Literal
from luna_usecases.abstract import Registry, UcSolution
@Registry.add
class KnapsackSolution(UcSolution):
"""Solution for the Knapsack use case.
Attributes
----------
name : Literal["knapsack"]
Identifier for this solution type.
# TODO: Add your solution field descriptions here
# Example:
# objective_value : float
# The objective value of the solution.
# selected_items : list[int]
# Indices of selected items.
# is_valid : bool
# Whether the solution satisfies all constraints.
"""
name: Literal["knapsack"] = "knapsack"
# TODO: Add your solution fields here
# Example:
# objective_value: float
# selected_items: list[int]
# is_valid: bool
def print(self) -> str:
"""Print the solution.
Returns
-------
str
String representation of the solution.
"""
lines = [
"Knapsack Solution:",
f" name: {self.name}",
# TODO: Add custom printing logic here
# Example:
# f" Objective: {self.objective_value}",
# f" Valid: {self.is_valid}",
]
return "\n".join(lines)
formulation.py - Optimization Model
This is the most important file - it contains your optimization logic.
"""Formulation for Knapsack use case."""
from typing import Literal
from luna_quantum import Model, Sense, Solution, Variable, Vtype, quicksum
from luna_usecases.abstract import Registry, UcFormulation
from luna_usecases.knapsack.data import KnapsackData
from luna_usecases.knapsack.solution import KnapsackSolution
# Threshold for interpreting binary variables
BINARY_DIVIDER = 0.5
@Registry.add
class KnapsackFormulation(
UcFormulation[KnapsackData, KnapsackSolution]
):
"""Constraint-based formulation for Knapsack.
This formulation uses Luna Model's constraint system rather than
manual QUBO penalty terms.
Mathematical Formulation
------------------------
Decision Variables:
# TODO: Define your variables
# Example: x_i ∈ {0,1} for each item i
Objective:
# TODO: Define your objective
# Example: maximize Σ_i (value_i * x_i)
Constraints:
# TODO: Define your constraints
# Example: Σ_i (weight_i * x_i) ≤ capacity
"""
name: Literal["knapsack"] = "knapsack"
@staticmethod
def print(data: KnapsackData) -> str:
"""Print the formulation.
Parameters
----------
data : KnapsackData
The problem data.
Returns
-------
str
String representation of the formulation.
"""
lines = [
"Knapsack Formulation:",
# TODO: Add formulation details
]
return "\n".join(lines)
@staticmethod
def formulate(data: KnapsackData) -> Model:
"""Formulate using constraint-based approach.
IMPORTANT: Use model.add_constraint() for constraints.
Do NOT use manual QUBO penalty terms (no A, B parameters).
Parameters
----------
data : KnapsackData
The problem data.
Returns
-------
Model
A Luna Model ready to be solved.
"""
# TODO: Implement the constraint-based formulation
# Example skeleton:
# model = Model(name=f"Knapsack_{data.name}")
#
# # Create variables
# with model.environment:
# x = {i: Variable(f"x_{i}", vtype=Vtype.Binary) for i in range(n)}
#
# # Objective (negate for maximization)
# objective = -quicksum(data.values[i] * x[i] for i in range(n))
#
# # Constraints (NOT penalties!)
# model.add_constraint(
# quicksum(data.weights[i] * x[i] for i in range(n)) <= data.capacity,
# name="capacity"
# )
#
# model.set_objective(objective)
# model.set_sense(Sense.Min)
#
# return model
raise NotImplementedError("Formulation not yet implemented")
@staticmethod
def interpret(solution: Solution, data: KnapsackData) -> KnapsackSolution:
"""Extract solution from quantum result.
Parameters
----------
solution : Solution
The quantum solution.
data : KnapsackData
The problem data.
Returns
-------
KnapsackSolution
Structured solution with metrics.
"""
best = solution.best()
if best is None:
raise ValueError("No solution found")
# TODO: Implement solution extraction
# Example:
# selected = []
# for i in range(data.n_items):
# if best.sample[f"x_{i}"] > BINARY_DIVIDER:
# selected.append(i)
#
# return KnapsackSolution(
# selected_items=selected,
# objective_value=sum(data.values[i] for i in selected),
# is_valid=check_constraints(selected, data),
# )
raise NotImplementedError("Interpretation not yet implemented")
instance.py - Combining Data and Formulation
Usually requires no modification:
"""Instance model for Knapsack use case."""
from luna_usecases.abstract import Registry, UcInstance
from luna_usecases.knapsack.data import KnapsackData
from luna_usecases.knapsack.formulation import KnapsackFormulation
from luna_usecases.knapsack.solution import KnapsackSolution
@Registry.add
class KnapsackInstance(
UcInstance[KnapsackData, KnapsackFormulation, KnapsackSolution]
):
"""Instance combining data and formulation for Knapsack."""
pass
collection.py - Instance Collections
Organize multiple problem instances for benchmarking:
"""Collection of Knapsack instances."""
import numpy as np
from luna_usecases.abstract import Registry, UcInstanceCollection
from luna_usecases.knapsack.data import KnapsackData
from luna_usecases.knapsack.formulation import KnapsackFormulation
from luna_usecases.knapsack.instance import KnapsackInstance
@Registry.add
class KnapsackCollection(UcInstanceCollection[KnapsackInstance]):
"""Collection of Knapsack instances.
This collection provides methods to generate benchmark instances with
various characteristics for testing and evaluation.
"""
instances: list[KnapsackInstance]
name: str
description: str
contributor: str
@classmethod
def from_random(
cls,
min_size: int,
max_size: int,
num_instances: int = 1,
*,
seed: int | None = None,
) -> "KnapsackCollection":
"""Generate random instances.
Parameters
----------
min_size : int
Minimum problem size.
max_size : int
Maximum problem size.
num_instances : int, optional
Number of instances per size, by default 1.
seed : int | None, optional
Random seed for reproducibility, by default None.
Returns
-------
KnapsackCollection
Collection containing generated instances.
"""
if seed is not None:
rng = np.random.default_rng(seed)
else:
rng = np.random.default_rng()
instances_list = []
for size in range(min_size, max_size + 1):
for _ in range(num_instances):
# TODO: Implement instance generation logic
# Example:
# data = KnapsackData.generate_random(
# size=size, seed=int(rng.integers(0, 2**31))
# )
# formulation = KnapsackFormulation()
# instances_list.append(
# KnapsackInstance(data=data, formulation=formulation)
# )
pass
return cls(
name=f"Random Knapsack Set {min_size}-{max_size}",
description=f"Random Knapsack instances with {num_instances} per size",
contributor="KnapsackCollection random generator",
instances=instances_list,
)
Using Your Use Case
Once implemented, import and use your use case:
from luna_usecases.knapsack import (
KnapsackCollection,
KnapsackData,
KnapsackFormulation,
KnapsackInstance,
KnapsackSolution,
)
# Create data
data = KnapsackData.generate_random(size=10, seed=42)
# Create formulation and instance
formulation = KnapsackFormulation()
instance = KnapsackInstance(data=data, formulation=formulation)
# Formulate and solve
model = instance.formulate()
# solution = solver.run(model).result()
# result = instance.interpret(solution)
Implementation Workflow
Define Your Data Model
Edit data.py to include all parameters needed for your problem. Add fields for problem size, constraints, and any input parameters.
Define Your Solution Structure
Edit solution.py to specify what information you want in the solution. Include fields for objective value, assignments, and validation status.
Implement the Formulation
This is the core work:
-
formulate(): Create the optimization model- Define decision variables
- Set the objective function
- Add constraints
-
interpret(): Convert solver output to your solution format- Extract variable values
- Calculate metrics
- Validate the solution
Test Your Implementation
Create a simple test script or notebook to verify your use case works correctly.
Add Collection Generators
Implement from_random() and other generator methods in your collection for benchmarking.
Next Steps
- Explore the IKP implementation as a reference for knapsack-style problems
- Explore the PAS implementation for a scheduling example
- Extend formulations for variants
Tips
Start Simple
Begin with a simple version of your problem, then add complexity incrementally.
Use Type Hints
Python type hints help catch errors early and make the code more maintainable.
Test Incrementally
Test each method (formulate(), interpret()) separately before running end-to-end.
Registry Decorator
The @Registry.add decorator registers your classes with the framework. This enables:
- Automatic serialization: Save and load instances to/from JSON
- Type resolution: The framework can reconstruct the correct class types when loading data
- Discovery: Your use case becomes available in the collection of registered use cases
- Interoperability: Works seamlessly with Luna Quantum solvers and benchmarking tools