Skip to content

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.

Python
"""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

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