# How to Define a Complete Configuration Space with Hyperparameter Specifications in Hypster

> Learn to define a complete configuration space in Hypster using Python functions and the HP object to declare hyperparameters. Validate and materialize concrete values with instantiate().

- Repository: [Gilad Rubin/hypster](https://github.com/gilad-rubin/hypster)
- Tags: how-to-guide
- Published: 2026-03-02

---

**You define a complete configuration space in Hypster by writing a Python function that uses the `HP` object to declare parameters with types, bounds, and optional HPO metadata, then calling `instantiate()` to validate and materialize concrete values.**

The Hypster library (`gilad-rubin/hypster`) provides a declarative API for building configuration spaces that bridge the gap between static configuration files and hyperparameter optimization frameworks like Optuna. By leveraging Python functions and the `HP` class, you can encode complex parameter relationships, validation rules, and search space definitions in a single, readable structure.

## The Core Architecture of Hypster Configuration Spaces

Hypster's architecture consists of several coordinated layers that transform a user-defined configuration function into a validated, optionally optimizable parameter space.

### The `instantiate` Entry Point

The primary entry point in [[`src/hypster/core.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/core.py)](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/core.py#L17) orchestrates the configuration flow. The `instantiate(config_func, values=...)` function:

1. Validates the signature of your configuration function
2. Creates an `HP` instance populated with supplied values
3. Executes the function to build the configuration space
4. Checks for unknown parameters via `_handle_unknown_parameters` (lines 73-106)

This final validation step can **warn**, **raise**, or **ignore** unused keys and even suggests similar names using `suggest_similar_names` from [[`src/hypster/utils.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/utils.py)](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/utils.py).

### The `HP` Class API

The [`HP` class](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/hp.py#L47) in [`src/hypster/hp.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp.py) provides the public interface for parameter declaration. Key methods include:

- **`int`** – Integer parameters with optional min/max bounds
- **`float`** – Floating-point values with precision control
- **`select`** – Single choice from a list of options
- **`multi_int`**, **`multi_float`**, **`multi_text`**, **`multi_bool`** – Multi-value parameters
- **`text`** – String parameters
- **`bool`** – Boolean flags
- **`nest`** – Nested configuration spaces

Each method delegates to specification objects (`SingleValueSpec`, `MultiValueSpec`, `SelectSingleSpec`, `SelectMultiSpec`) and low-level handlers that manage parameter registration.

### Validation and Type Safety

The validators in [[`src/hypster/hp_calls.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp_calls.py)](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/hp_calls.py) enforce constraints at definition time:

- **`IntValidator`** and **`FloatValidator`** perform **bounds checking** via `validate_bounds` to ensure values satisfy `min`/`max` constraints
- **`SelectValidator`** validates categorical choices against available options
- **`MultiValidator`** handles multi-value parameter validation

The optional **`strict`** flag prevents silent type down-casting, while **name validation** ensures the `name=` parameter is supplied when using value overrides.

### HPO Metadata Definitions

For hyperparameter optimization, [`src/hypster/hpo/types.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hpo/types.py) defines data classes that extend parameters with search space metadata:

- **`HpoInt`** – Configures step sizes, logarithmic scaling (`scale="log"`), and inclusive-max flags
- **`HpoFloat`** – Supports step quantization, scaling modes, and distributions like `loguniform`
- **`HpoCategorical`** – Enables ordered categorical flags and optional weight vectors for sampling bias

Pass these as the `hpo_spec=` argument to any `HP` method to enable Optuna integration.

### Optuna Integration via `_HPProxy`

When optimizing, the [`_HPProxy` class](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/hpo/optuna.py) in [`src/hypster/hpo/optuna.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hpo/optuna.py) mirrors the `HP` API but forwards each call to `trial.suggest_*`. The proxy records suggested values in a collector dictionary, allowing you to retrieve the exact sampled configuration after the trial completes.

### Namespace Handling with Nested Configurations

The **`nest`** method creates hierarchical parameter spaces by instantiating a new `HP` object with an extended namespace (`parent.child.param`). This mechanism:

- Tracks nested calls to prevent name collisions
- Propagates parameter access information upward
- Enables conditional configuration blocks

Use nesting to create modular, reusable configuration components that merge into a unified space.

## Step-by-Step: Building a Configuration Function

Define your configuration space by writing a function that accepts an `HP` instance and declares parameters using the methods above:

```python

# file: my_config.py

from hypster import HP
from hypster.hpo.types import HpoInt, HpoFloat, HpoCategorical

def model_cfg(hp: HP):
    # Integer with bounds and HPO step specification

    batch_size = hp.int(
        32,
        name="batch_size",
        min=8,
        max=256,
        hpo_spec=HpoInt(step=8, scale="log")
    )

    # Float with log-uniform distribution for optimization

    lr = hp.float(
        0.001,
        name="learning_rate",
        min=1e-5,
        max=1e-1,
        hpo_spec=HpoFloat(scale="log", distribution="loguniform")
    )

    # Categorical selection with sampling weights

    optimizer = hp.select(
        ["adam", "sgd", "rmsprop"],
        name="optimizer",
        default="adam",
        hpo_spec=HpoCategorical(weights=[0.6, 0.3, 0.1])
    )

    # Multi-boolean flags

    flags = hp.multi_bool([True, False], name="flags")

    # Conditional nesting: only expose dropout for Adam optimizer

    def dropout_cfg(hp):
        return hp.float(0.5, name="dropout", min=0.0, max=1.0)

    if optimizer == "adam":
        hp.nest(dropout_cfg, name="dropout_cfg")

    return {
        "batch_size": batch_size,
        "lr": lr,
        "optimizer": optimizer,
        "flags": flags,
    }

```

This example demonstrates **type declaration**, **bounds enforcement**, **HPO metadata injection**, and **conditional parameter exposure** through nesting.

## Instantiating Configurations with Concrete Values

To materialize a configuration with specific values, use the `instantiate` function from [`src/hypster/core.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/core.py):

```python
from hypster import instantiate
from my_config import model_cfg

values = {
    "batch_size": 64,
    "learning_rate": 0.01,
    "optimizer": "sgd",
    "flags": [True, True],
}

cfg = instantiate(model_cfg, values=values)
print(cfg)

# Output: {'batch_size': 64, 'lr': 0.01, 'optimizer': 'sgd', 'flags': [True, True]}

```

The `instantiate` call validates the supplied dictionary against your declarations, raises `ValueError` for missing or invalid keys, and returns the configuration dictionary produced by your function.

## Integrating with Optuna for Hyperparameter Optimization

To sample from the complete search space defined in your configuration, use the Optuna integration:

```python
import optuna
from hypster.hpo.optuna import suggest_values
from my_config import model_cfg

def objective(trial):
    # Sample values according to HPO specifications

    sampled = suggest_values(trial, config=model_cfg)
    
    # Build and evaluate your model using sampled parameters

    # Returns a scalar loss for minimization

    return (sampled["lr"] - 0.01) ** 2 + (sampled["batch_size"] - 64) ** 2

study = optuna.create_study(direction="minimize")
study.optimize(objective, n_trials=30)

print("Best hyperparameters:", study.best_params)

```

The `suggest_values` function (lines 39-51 in [`src/hypster/hpo/optuna.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hpo/optuna.py)) uses the `_HPProxy` to translate your `HP` calls into Optuna's `trial.suggest_*` methods, automatically respecting the `HpoInt`, `HpoFloat`, and `HpoCategorical` specifications.

## Error Handling and Validation

Hypster provides robust error handling through the validation layers in [`hp_calls.py`](https://github.com/gilad-rubin/hypster/blob/main/hp_calls.py). When `instantiate` detects parameters supplied in the `values` dictionary that were never accessed during configuration execution, it invokes `_handle_unknown_parameters` with configurable behavior:

- **Strict mode**: Raises an error for any unknown key
- **Warning mode**: Logs suggestions for similar valid names using `suggest_similar_names`
- **Ignore mode**: Silently drops unused parameters

This prevents silent failures from typos in parameter names and ensures configuration integrity across refactoring.

## Summary

- **Define parameter spaces** using Python functions that accept an `HP` object and call methods like `int()`, `float()`, `select()`, and `multi_bool()`.
- **Enforce constraints** through the `min`, `max`, `strict`, and `name` parameters, validated by classes in [`src/hypster/hp_calls.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp_calls.py).
- **Enable optimization** by passing `hpo_spec` objects (`HpoInt`, `HpoFloat`, `HpoCategorical`) to integrate with Optuna via [`src/hypster/hpo/optuna.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hpo/optuna.py).
- **Materialize configurations** using `instantiate()` from [`src/hypster/core.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/core.py), which validates inputs and handles unknown parameter detection.
- **Nest configurations** using `hp.nest()` to create modular, hierarchical spaces with automatic namespace management.

## Frequently Asked Questions

### How do I create conditional parameters that only appear under certain values?

Use the **`nest`** method to conditionally expose parameter sub-spaces. Define a child configuration function and wrap it in an `if` statement based on the parent parameter value. For example, only expose `dropout_rate` when `optimizer == "adam"` by nesting the dropout configuration inside a conditional block. The `HP` class tracks namespace extensions automatically, ensuring child parameters are properly scoped.

### What validation errors can Hypster catch during instantiation?

Hypster validates parameter **types**, **bounds** (min/max constraints), **categorical choices**, and **required names** through the validator classes in [`src/hypster/hpo/hp_calls.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hpo/hp_calls.py). The `instantiate` function in [`src/hypster/core.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/core.py) also detects **unknown parameters**—values supplied but never accessed—and can suggest similar valid names using fuzzy matching via `suggest_similar_names`.

### How do I specify logarithmic scaling for hyperparameter optimization?

Pass an **`HpoInt`** or **`HpoFloat`** instance to the `hpo_spec=` argument of the corresponding `HP` method. Set `scale="log"` to enable logarithmic scaling, or use `distribution="loguniform"` for `HpoFloat` to sample from a log-uniform distribution. These specifications are interpreted by the `_HPProxy` class when using Optuna integration.

### Can I define multi-valued parameters like lists of integers or booleans?

Yes. Use the **`multi_int`**, **`multi_float`**, **`multi_text`**, or **`multi_bool`** methods on the `HP` object. These create `MultiValueSpec` instances validated by `MultiValidator`, allowing you to specify lists of values with optional element-wise constraints and defaults.