Skip to content

Basic Usage

This page covers the fundamental usage patterns of bounded-models.

Creating a Bounded Model

Using BoundedModel Base Class

The simplest way to ensure your model is bounded is to inherit from BoundedModel:

from bounded_models import BoundedModel
from pydantic import Field

class SearchParams(BoundedModel):
    learning_rate: float = Field(ge=1e-5, le=1e-1)
    batch_size: int = Field(ge=1, le=128)

BoundedModel validates at class definition time that all fields are properly bounded. If a field is unbounded, a ValueError is raised immediately.

Using Regular BaseModel

You can also use regular Pydantic BaseModel and check boundedness manually:

from pydantic import BaseModel, Field
from bounded_models import FieldHandlerRegistry

class Config(BaseModel):
    threshold: float = Field(ge=0.0, le=1.0)
    max_iterations: int = Field(ge=1, le=1000)

registry = FieldHandlerRegistry.default()

# Check if bounded
is_bounded = registry.check_model_boundedness(Config)  # True

Calculating Dimensions

The dimensionality of a model is the number of independent parameters:

from typing import Literal
from bounded_models import FieldHandlerRegistry
from pydantic import BaseModel, Field

class ExperimentConfig(BaseModel):
    algorithm: Literal["sgd", "adam", "rmsprop"]  # 1 dimension
    learning_rate: float = Field(ge=1e-5, le=1e-1)  # 1 dimension
    momentum: float = Field(ge=0.0, le=0.99)  # 1 dimension

registry = FieldHandlerRegistry.default()
dims = registry.model_dimensions(ExperimentConfig)  # 3

Sampling Model Instances

Sample instances by providing values in the unit hypercube [0, 1]^n:

from bounded_models import FieldHandlerRegistry
from pydantic import BaseModel, Field

class Params(BaseModel):
    x: float = Field(ge=0.0, le=10.0)
    y: float = Field(ge=-5.0, le=5.0)

registry = FieldHandlerRegistry.default()

# Sample at specific point
instance = registry.sample_model([0.0, 0.5], Params)
# Params(x=0.0, y=0.0)

instance = registry.sample_model([1.0, 1.0], Params)
# Params(x=10.0, y=5.0)

instance = registry.sample_model([0.5, 0.25], Params)
# Params(x=5.0, y=-2.5)

Nested Models

Bounded models can be nested:

from bounded_models import BoundedModel
from pydantic import Field

class Position(BoundedModel):
    x: float = Field(ge=0.0, le=100.0)
    y: float = Field(ge=0.0, le=100.0)

class Agent(BoundedModel):
    position: Position  # 2 dimensions from nested model
    speed: float = Field(ge=0.0, le=10.0)  # 1 dimension

registry = FieldHandlerRegistry.default()
dims = registry.model_dimensions(Agent)  # 3

Integration with Quasi-Random Sequences

bounded-models works well with quasi-random sequences for space-filling designs:

import numpy as np
from scipy.stats import qmc
from bounded_models import FieldHandlerRegistry
from pydantic import BaseModel, Field

class SearchSpace(BaseModel):
    param_a: float = Field(ge=0.0, le=1.0)
    param_b: float = Field(ge=0.0, le=1.0)
    param_c: float = Field(ge=0.0, le=1.0)

registry = FieldHandlerRegistry.default()
dims = registry.model_dimensions(SearchSpace)

# Generate Sobol sequence
sampler = qmc.Sobol(d=dims, scramble=True)
samples = sampler.random(n=10)

# Convert to model instances
instances = [registry.sample_model(list(s), SearchSpace) for s in samples]