Skip to content

Decorators

feature

feature(_cls: type[T] | Callable[[Model], FeatureResult | BaseModel | object] | None = None, *, feature_id: str | None = None, feature_registry: Registry[BaseFeature] = Provide[RegistryContainer.feature_registry]) -> Callable[[type[T] | Callable[[Model], FeatureResult | BaseModel | object]], type[BaseFeature[FeatureResult]] | type[T]] | type[BaseFeature[FeatureResult]] | type[T]

feature(_cls: type[T], *, feature_id: str | None = None, feature_registry: Registry[BaseFeature] = Provide[RegistryContainer.feature_registry]) -> type[T]
feature(_cls: Callable[[Model], FeatureResult | BaseModel | object], *, feature_id: str | None = None, feature_registry: Registry[BaseFeature] = Provide[RegistryContainer.feature_registry]) -> type[BaseFeature[FeatureResult]]
feature(_cls: None = None, *, feature_id: str | None = None, feature_registry: Registry[BaseFeature] = Provide[RegistryContainer.feature_registry]) -> Callable[[type[T] | Callable[[Model], FeatureResult | BaseModel | object]], type[BaseFeature[FeatureResult]] | type[T]]

Register a class or function as a feature.

The decorated class must be a subclass of the BaseFeature protocol, or a decorated function will be automatically wrapped as a BaseFeature subclass.

Parameters:

  • _cls (type[T], default: None ) –

    The class to be decorated. If None, returns a decorator function.

  • feature_id (str | None, default: None ) –

    Set a custom ID for the feature. If not provided, the ID will be generated automatically from the module and class/function name. It's recommended to not set this parameter.

  • feature_registry ((Registry[BaseFeature], injected), default: Provide[feature_registry] ) –

    The registry where the feature will be registered. Injected by dependency container.

Returns:

  • Callable[[type[T]], type[T]] | type[T]

    Either the decorated class/function or a decorator function.

Examples:

Decorate a class as a feature:

>>> from luna_bench.base_components import BaseFeature
>>> from luna_model import Model
>>> from pydantic import BaseModel
>>>
>>> @feature
... class MyFeature(BaseFeature):
...     def run(self, model: Model) -> BaseModel:
...         # Extract and return feature data
...         return {"feature_value": 42}

Decorate a function as a feature:

>>> @feature
... def image_dimensions(model: Model) -> dict:
...     # Extract image dimensions from model
...     return {"width": 640, "height": 480}

Use a custom feature ID:

>>> @feature(feature_id="custom.image_dimensions")
... def image_dims(model: Model) -> dict:
...     return {"width": 640, "height": 480}

algorithm

algorithm(_cls: type[T] | Callable[[Model], Solution] | None = None, *, algorithm_id: str | None = None, algorithm_sync_registry: Registry[BaseAlgorithmSync] = Provide[RegistryContainer.algorithm_sync_registry], algorithm_async_registry: Registry[BaseAlgorithmAsync[Any]] = Provide[RegistryContainer.algorithm_async_registry]) -> Callable[[type[T] | Callable[[Model], Solution]], type[T] | type[BaseAlgorithmSync]] | type[T] | type[BaseAlgorithmSync]

algorithm(_cls: type[T], *, algorithm_id: str | None = None) -> type[T]
algorithm(_cls: Callable[[Model], Solution], *, algorithm_id: str | None = None) -> type[BaseAlgorithmSync]
algorithm(_cls: None = None, *, algorithm_id: str | None = None) -> Callable[[type[T] | Callable[[Model], Solution]], type[T] | type[BaseAlgorithmSync]]

Register a class or function as an algorithm.

The decorated class must be a subclass of the BaseAlgorithmSync or BaseAlgorithmAsync protocol. When decorating a function, it must be a synchronous algorithm that takes a Model and returns a Solution.

Parameters:

  • _cls (type[T] | Callable[[Model], Solution], default: None ) –

    The class or function to be decorated. If None, returns a decorator function.

  • algorithm_id (str | None, default: None ) –

    Set a custom ID for the algorithm. If not provided, the ID will be generated automatically from the module and class/function name. It's recommended to not set this parameter.

  • algorithm_sync_registry ((Registry[BaseAlgorithmSync], injected), default: Provide[algorithm_sync_registry] ) –

    The registry where synchronous algorithms will be registered. Injected by dependency container.

  • algorithm_async_registry ((Registry[BaseAlgorithmAsync[Any]], injected), default: Provide[algorithm_async_registry] ) –

    The registry where asynchronous algorithms will be registered. Injected by dependency container.

Returns:

  • Callable[[type[T]], type[T]] | type[T]

    Either the decorated class/function or a decorator function.

Examples:

Decorate a class as a synchronous algorithm:

>>> from luna_bench.base_components import BaseAlgorithmSync
>>> from luna_model import Model, Solution
>>>
>>> @algorithm
... class MyAlgorithm(BaseAlgorithmSync):
...     def run(self, model: Model) -> Solution:
...         # Run algorithm and return solution
...         return Solution(...)

Decorate a function as a synchronous algorithm:

>>> @algorithm
... def simple_algorithm(model: Model) -> Solution:
...     # Run algorithm and return solution
...     return Solution(...)

Decorate a class as an asynchronous algorithm:

>>> from luna_bench.base_components import BaseAlgorithmAsync
>>> from pydantic import BaseModel
>>> from returns.result import Result
>>>
>>> class QuantumState(BaseModel):
...     job_id: str
>>> @algorithm
... class QuantumAlgorithm(BaseAlgorithmAsync[QuantumState]):
...     @property
...     def model_type(self) -> type[QuantumState]:
...         return QuantumState
...
...     def run_async(self, model: Model) -> QuantumState:
...         # Submit job and return retrieval data
...         return QuantumState(job_id="123")
...
...     def fetch_result(self, model: Model, retrieval_data: QuantumState) -> Result[Solution, str]:
...         # Fetch result using retrieval data
...         return Ok(Solution(...))

Use a custom algorithm ID:

>>> @algorithm(algorithm_id="custom.my_algorithm")
... def my_algorithm(model: Model) -> Solution:
...     return Solution(...)

metric

metric(_cls: type[BaseMetric[Any]] | Callable[[Solution, FeatureResultContainer], MetricResult | float | int] | list[type[BaseFeature[Any]]] | tuple[type[BaseFeature[Any]], ...] | type[BaseFeature[Any]] | None = None, *, metric_id: str | None = None, metric_registry: Registry[BaseMetric] = Provide[RegistryContainer.metric_registry]) -> Callable[[type[T] | Callable[[Solution, FeatureResultContainer], MetricResult | float | int]], type[T] | type[BaseMetric[MetricResult]]] | type[T] | type[BaseMetric[MetricResult]]

metric(_cls: type[T], *, metric_id: str | None = None, metric_registry: Registry[BaseMetric[Any]] = Provide[RegistryContainer.metric_registry]) -> type[T]
metric(_cls: Callable[[Solution, FeatureResultContainer], MetricResult | float | int], *, metric_id: str | None = None, metric_registry: Registry[BaseMetric[Any]] = Provide[RegistryContainer.metric_registry]) -> type[BaseMetric[MetricResult]]
metric(_cls: type[BaseFeature[Any]], *, metric_id: str | None = None, metric_registry: Registry[BaseMetric[Any]] = Provide[RegistryContainer.metric_registry]) -> Callable[[type[BaseMetric[Any]] | Callable[[Solution, FeatureResultContainer], MetricResult | float | int]], type[BaseMetric[Any]] | type[BaseMetric[MetricResult]]]
metric(_cls: list[type[BaseFeature[Any]]] | tuple[type[BaseFeature[Any]], ...], *, metric_id: str | None = None, metric_registry: Registry[BaseMetric[Any]] = Provide[RegistryContainer.metric_registry]) -> Callable[[type[BaseMetric[Any]] | Callable[[Solution, FeatureResultContainer], MetricResult | float | int]], type[BaseMetric[Any]] | type[BaseMetric[MetricResult]]]
metric(_cls: None = None, *, metric_id: str | None = None, metric_registry: Registry[BaseMetric[Any]] = Provide[RegistryContainer.metric_registry]) -> Callable[[type[T] | Callable[[Solution, FeatureResultContainer], MetricResult | float | int]], type[T] | type[BaseMetric[MetricResult]]]

Register a class or function as a metric component.

This decorator handles both class-based metrics (inheriting from BaseMetric) and function-based metrics. When you decorate a function, it's automatically wrapped in a BaseMetric subclass. You can also declare feature dependencies that your metric needs to compute its result.

Parameters:

  • _cls (type[BaseMetric[Any]] | Callable[[Solution, FeatureResultContainer], MetricResult | float | int] | list[type[BaseFeature[Any]]] | tuple[type[BaseFeature[Any]], ...] | type[BaseFeature[Any]] | None, default: None ) –

    The metric to register. Can be:

    • A class inheriting from BaseMetric (bare @metric)
    • A function taking (solution, feature_results) and returning float/int/MetricResult
    • A single BaseFeature type (becomes a dependency)
    • A list or tuple of BaseFeature types (dependencies)

    When dependencies are passed, the decorator returns a decorator function that expects a metric class or function as its argument. In mose cases you don't need to set this field at all.

  • metric_id (str | None, default: None ) –

    Custom identifier for this metric in the registry. If omitted, an ID is auto-generated from the module and class/function name.

  • metric_registry (Registry[BaseMetric], default: Provide[metric_registry] ) –

    The registry where this metric will be stored. Injected by the container. You do not need to set it.

Returns:

Notes

When decorating a function, the return type can be:

  • float or int: automatically wrapped in a MetricResult
  • MetricResult: returned as-is

The decorated function's signature is validated to ensure it matches the expected (solution, feature_results) parameters.

Examples:

Basic metric from a class:

>>> from luna_bench.base_components import BaseMetric
>>> from luna_model import Solution
>>> from luna_bench.base_components.data_types.feature_results import FeatureResults
>>> from luna_bench.types import MetricResult
>>>
>>> @metric
... class SolutionQuality(BaseMetric):
...     def run(self, solution: Solution, feature_results: FeatureResults) -> MetricResult:
...         ...
...         return MetricResult(result=26.11)

Basic metric from a function:

>>> @metric
... def solve_time(solution: Solution, feature_results: FeatureResultContainer) -> float:
...     return 26.11

Metric that depends on a feature:

>>> from luna_bench.base_components import BaseFeature
>>>
>>> @metric(Feature1)
... def feature_based_metric(solution: Solution, feature_results: FeatureResultContainer) -> float:
...     return 26.11

Metric depending on multiple features:

>>> @metric([Feature1, Feature2])
... def multi_feature_metric(solution: Solution, feature_results: FeatureResultContainer) -> float:
...     return 26.11

plot

plot(required_components: type[BasePlot] | Callable[[BenchmarkResultContainer], None] | list[type[_REQUIRED_TYPES]] | tuple[type[_REQUIRED_TYPES], ...] | type[_REQUIRED_TYPES] | None = None, *, plot_id: str | None = None, plot_registry: Registry[BasePlot] = Provide[RegistryContainer.plot_registry]) -> Callable[[type[BasePlot] | Callable[[BenchmarkResultContainer], None]], type[BasePlot]] | type[BasePlot]

plot(required_components: type[BasePlot], *, plot_id: str | None = None, plot_registry: Registry[BasePlot] = Provide[RegistryContainer.plot_registry]) -> type[BasePlot]
plot(required_components: Callable[[BenchmarkResultContainer], None], *, plot_id: str | None = None, plot_registry: Registry[BasePlot] = Provide[RegistryContainer.plot_registry]) -> type[BasePlot]
plot(required_components: type[_REQUIRED_TYPES], *, plot_id: str | None = None, plot_registry: Registry[BasePlot] = Provide[RegistryContainer.plot_registry]) -> Callable[[type[BasePlot] | Callable[[BenchmarkResultContainer], None]], type[BasePlot]]
plot(required_components: list[type[_REQUIRED_TYPES]] | tuple[type[_REQUIRED_TYPES], ...], *, plot_id: str | None = None, plot_registry: Registry[BasePlot] = Provide[RegistryContainer.plot_registry]) -> Callable[[type[BasePlot] | Callable[[BenchmarkResultContainer], None]], type[BasePlot]]
plot(required_components: None = None, *, plot_id: str | None = None, plot_registry: Registry[BasePlot] = Provide[RegistryContainer.plot_registry]) -> Callable[[type[BasePlot] | Callable[[BenchmarkResultContainer], None]], type[BasePlot]]

Register a class or function as a plot component.

This decorator registers visualization components that consume benchmark results. You can register a class inheriting from BasePlot or wrap a function that handles the plotting logic. Plots can declare dependencies on specific metrics or features they need to function.

Parameters:

  • required_components (type[BasePlot] | Callable[[BenchmarkResultContainer], None] | list[type[_REQUIRED_TYPES]] | tuple[type[_REQUIRED_TYPES], ...] | type[_REQUIRED_TYPES] | None, default: None ) –

    The plot to register or its dependencies. Can be:

    • A class inheriting from BasePlot (bare @plot)
    • A function taking benchmark_results and returning None
    • A single BaseMetric or BaseFeature type (becomes a dependency)
    • A list or tuple of BaseMetric and/or BaseFeature types (dependencies)

    When dependencies are passed, the decorator returns a decorator function that expects a plot class or function as its argument.

  • plot_id (str | None, default: None ) –

    Custom identifier for this plot in the registry. If omitted, an ID is auto-generated from the module and class/function name.

  • plot_registry (Registry[BasePlot], default: Provide[plot_registry] ) –

    The registry where this plot will be stored. Injected by the container. You do not need to set it.

Returns:

  • type[BasePlot] | Callable

    The registered plot class, or a decorator if dependencies were specified.

Notes

When decorating a function, it will be wrapped in a BasePlot subclass automatically. The function receives the full BenchmarkResults object and is responsible for extracting the data it needs to render the plot.

Examples:

Basic plot from a class:

>>> from luna_bench.custom import BasePlot, BenchmarkResults
>>>
>>> @plot
... class MyPlot(BasePlot):
...     def run(self, benchmark_results: BenchmarkResults) -> None:
...         # Plot or visualization the data

Basic plot from a function:

>>> @plot
... def quick_plot(benchmark_results: BenchmarkResultContainer) -> None:
...      # Plot or visualization the data

Plot that depends on a specific metric:

>>> from luna_bench.metrics import Runtime
>>>
>>> @plot(Runtime)
... class RuntimeVisualization(BasePlot):
...     def run(self, benchmark_results: BenchmarkResultContainer) -> None:
...         # Plot or visualization the data

Plot that depends on multiple metrics:

>>> @plot([Runtime, Feasibility])
... class ComparisonPlot(BasePlot):
...     def run(self, benchmark_results: BenchmarkResultContainer) -> None:
...         # Render side-by-side comparison