HumaLab SDK

Scenarios

Scenarios are the foundation of HumaLab's adaptive validation system. They define parameterized configurations using probability distributions, enabling systematic exploration of your AI system's behavior.

Creating a Scenario

import humalab as hl
 
scenario = hl.scenarios.Scenario()
scenario.init(scenario={
    "gravity": "${uniform(-9.8, -8.8)}",
    "friction": "${gaussian(0.5, 0.1)}",
    "wind_speed": "${truncated_gaussian(0, 2, 0, 10)}"
})

Scenario GUI

scenario_main

From the Scenario Main page, you can add new scenarios, or view the existing scenarios Click "Add Scenario" icon

Using the GUI to create a Scenario

Scenario Creation page

Add the Scenario name, and other optional details to create a new scenario. If you have the YAML configuration ready, you can also add it here.

Using the GUI add or edit YAML Configuration

Scenario Detail page

After creating a new scenario, or selected an exisiting scenario from the main page, you can edit the YAML configuration by clicking "Edit".

Supported Distributions

HumaLab supports a variety of probability distributions for scenario parameters:

Scalar Distributions (0D)

Uniform Distribution

Samples uniformly between min and max values.

"${uniform(min, max)}"
# Example: "${uniform(0, 10)}"

Gaussian (Normal) Distribution

Samples from a normal distribution with specified mean and standard deviation.

"${gaussian(mean, std)}"
# Example: "${gaussian(5.0, 1.0)}"

Truncated Gaussian

Samples from a normal distribution bounded by min and max values.

"${truncated_gaussian(mean, std, min, max)}"
# Example: "${truncated_gaussian(5.0, 1.0, 0, 10)}"

Log-Uniform Distribution

Samples uniformly in log space, useful for parameters that span multiple orders of magnitude.

"${log_uniform(min, max)}"
# Example: "${log_uniform(0.001, 1.0)}"

Bernoulli Distribution

Binary distribution returning 0 or 1.

"${bernoulli(p)}"
# Example: "${bernoulli(0.5)}"

Categorical Distribution

Samples from a list of choices (numbers or strings) with optional weights.

"${categorical(choices, weights)}"
# Example: "${categorical(['red', 'green', 'blue'], [0.5, 0.3, 0.2])}"
# Example: "${categorical([10, 20, 30], [0.5, 0.3, 0.2])}"
# Example: "${categorical(['A', 'B', 'C'])}"  # Uniform if weights not provided

Discrete Distribution

Samples integer values uniformly from a range [low, high).

"${discrete(low, high, endpoint)}"
# Example: "${discrete(1, 10)}"  # Samples from [1, 10) - excludes 10
# Example: "${discrete(1, 10, true)}"  # Samples from [1, 10] - includes 10
# Example: "${discrete(0, 100, false)}"  # Samples from [0, 100) - excludes 100

Multi-Dimensional Distributions

HumaLab supports 1D, 2D, and 3D versions of several distributions. Important: 1D distributions use the same scalar parameters as 0D but return a 1-element array. 2D and 3D use array parameters.

# 1D Uniform (same parameters as 0D, returns array of size 1)
"position_x": "${uniform_1d(-10, 10)}"
 
# 1D Gaussian (same parameters as 0D, returns array of size 1)
"velocity": "${gaussian_1d(5.0, 1.0)}"
 
# 2D Gaussian (array parameters for 2D output)
"coordinates": "${gaussian_2d([0, 0], [1, 1])}"
 
# 3D Uniform (array parameters for 3D output)
"position_3d": "${uniform_3d([-5, -5, 0], [5, 5, 10])}"

Parameter Rules:

  • 0D (scalar): uniform(min, max) → returns single value
  • 1D: uniform_1d(min, max) → returns [value] (1-element array, same scalar params)
  • 2D: uniform_2d([min, min], [max, max]) → returns [value1, value2] (array params)
  • 3D: uniform_3d([min, min, min], [max, max, max]) → returns [value1, value2, value3] (array params)

Scenario Configuration

YAML String Format

You can define scenarios using YAML strings:

scenario_yaml = """
environment:
  gravity: ${uniform(-9.8, -8.8)}
  temperature: ${gaussian(20, 5)}
 
robot:
  initial_position: ${uniform_2d([-1, -1], [1, 1])}
  mass: ${log_uniform(0.5, 2.0)}
 
task:
  difficulty: ${categorical(['easy', 'medium', 'hard'], [0.5, 0.3, 0.2])}
"""
 
scenario.init(scenario=scenario_yaml)

Dictionary Format

Alternatively, use Python dictionaries:

scenario_dict = {
    "environment": {
        "gravity": "${uniform(-9.8, -8.8)}",
        "temperature": "${gaussian(20, 5)}"
    },
    "robot": {
        "initial_position": "${uniform_2d([-1, -1], [1, 1])}",
        "mass": "${log_uniform(0.5, 2.0)}"
    }
}
 
scenario.init(scenario=scenario_dict)

Resolving Scenarios

When you create an episode, the scenario is resolved by sampling from all distributions:

# The scenario template
print(scenario.yaml)
 
# Create a run and episode
run = hl.Run(scenario=scenario, project="test")
episode = run.create_episode()
 
# Get the resolved values
resolved_scenario, episode_vals = scenario.resolve()
print(resolved_scenario)  # Concrete values sampled from distributions

Using Scenarios with init()

You can pass a Scenario instance directly to init() for more control over scenario initialization:

import humalab as hl
from humalab.scenarios import Scenario
 
# Create and initialize a scenario
scenario = Scenario()
scenario.init(
    scenario={
        "gravity": "${uniform(-9.8, -8.8)}",
        "friction": "${gaussian(0.5, 0.1)}",
        "wind_speed": "${truncated_gaussian(0, 2, 0, 10)}"
    },
    seed=42  # Set seed for reproducibility
)
 
# Use the scenario instance in init()
with hl.init(
    project="physics_sim",
    name="controlled_test",
    scenario=scenario,  # Pass Scenario instance
    api_key="your_api_key"
) as run:
    for i in range(10):
        with run.create_episode() as episode:
            print(f"Gravity: {episode.gravity}")
            print(f"Friction: {episode.friction}")
            # Run your tests...

Alternative: Pass scenario dict directly to init():

with hl.init(
    project="physics_sim",
    name="inline_test",
    scenario={
        "gravity": "${uniform(-9.8, -8.8)}",
        "friction": "${gaussian(0.5, 0.1)}"
    },
    seed=42,  # Seed can be passed here too
    api_key="your_api_key"
) as run:
    # Scenario is automatically initialized
    pass

Scenario IDs and Versions

You can specify scenario IDs and versions for tracking:

scenario.init(
    scenario=scenario_dict,
    scenario_id="my_scenario:1",  # Format: "name:version"
    seed=42  # Optional seed for reproducibility
)

Accessing Scenario Data

Template Access

# Get the scenario template (with distribution placeholders)
template = scenario.template
yaml_str = scenario.yaml
 
# Get scenario metadata
scenario_id = scenario.scenario_id  # The scenario ID
seed = scenario.seed                # The random seed (if set)

Example:

scenario = Scenario()
scenario.init(
    scenario={"param": "${uniform(0, 10)}"},
    scenario_id="my_scenario:1",
    seed=42
)
 
print(f"ID: {scenario.scenario_id}")    # Output: my_scenario
print(f"Seed: {scenario.seed}")          # Output: 42
print(f"YAML:\n{scenario.yaml}")

Episode-Specific Values

# Each episode has its own sampled values
episode = run.create_episode()
# Episode values are automatically tracked and uploaded

Best Practices

  1. Use Appropriate Distributions: Choose distributions that match your parameter's characteristics

    • Use uniform for bounded parameters without a preferred value
    • Use gaussian for parameters with a natural center value
    • Use log_uniform for parameters spanning multiple orders of magnitude
  2. Set Reasonable Bounds: Ensure distribution parameters create valid values for your use case

  3. Version Your Scenarios: Use scenario IDs and versions to track changes over time

  4. Test Resolution: Verify that scenario resolution produces valid configurations:

    for i in range(10):
        resolved, vals = scenario.resolve()
        print(f"Sample {i}: {resolved}")
  5. Document Your Distributions: Include comments explaining why specific distributions and parameters were chosen

On this page