Skip to main content

SMDP-level behavior for Gymnasium environments with options and durations

Project description

SMDPfier

PyPI Version Python Version License Build Status Documentation Coverage

Add SMDP-level behavior to any Gymnasium environment with options and simple temporal semantics.

SMDPfier is a Gymnasium wrapper that enables Semi-Markov Decision Process (SMDP) behavior by letting you execute Options (sequences of primitive actions) where each primitive action = 1 tick of time, enabling natural SMDP discounting with γ^{k}.

🚀 Quick Start

Index Interface (Recommended for RL)

import gymnasium as gym
from smdpfier import SMDPfier, Option

# Create environment and define options
env = gym.make("CartPole-v1")
options = [
    Option(actions=[0, 0, 1], name="left-left-right"),     # 3 actions = 3 ticks
    Option(actions=[1, 1, 0], name="right-right-left"),    # 3 actions = 3 ticks
    Option(actions=[0, 1], name="left-right"),              # 2 actions = 2 ticks
]

# Wrap with SMDPfier
smdp_env = SMDPfier(
    env,
    options_provider=options,           # Static options list
    action_interface="index",           # Discrete(3) action space
    max_options=len(options)
)

# Use it like any Gym environment
obs, info = smdp_env.reset()
obs, reward, term, trunc, info = smdp_env.step(0)  # Execute first option

# Access SMDP metadata
smdp_info = info["smdp"]
print(f"Option: {smdp_info['option']['name']}")
print(f"Duration: {smdp_info['duration']} ticks (= k_exec)")
print(f"Per-step rewards: {smdp_info['rewards']}")
print(f"Macro reward: {reward}")  # sum of per-step rewards

# Apply SMDP discounting: γ^{duration}
gamma = 0.99
discounted_reward = reward * (gamma ** smdp_info['duration'])

Direct Interface (Intuitive)

# Pass Option objects directly
smdp_env = SMDPfier(env, options_provider=options, action_interface="direct")

# Execute with Option objects
obs, reward, term, trunc, info = smdp_env.step(options[0])

🎯 Key Features

  • ⏱️ Simple Time Semantics: Each primitive action = 1 tick, duration = k_exec
  • 🔗 Flexible Options: Static sequences or dynamic discovery via callable
  • 🎛️ Two Interfaces: Index-based (Discrete actions) or direct Option passing
  • 📊 SMDP Discounting: Natural γ^{k} discounting where k = number of primitive actions
  • 🎭 Action Masking: Support for discrete action availability constraints
  • 📋 Rich Info: Comprehensive execution metadata in info["smdp"]
  • 🛡️ Error Handling: Detailed validation and runtime error reporting
  • 🔄 Continuous Actions: Full support for continuous action spaces
  • 🎲 Built-in Defaults: Ready-to-use option generators and reward aggregators

📖 Core Concepts

Options

Options are sequences of primitive actions executed atomically:

# Simple option with 3 primitive actions
option = Option(actions=[0, 1, 0], name="left-right-left")

Time Semantics (v0.2.0+)

Simple and natural:

  • Each primitive action = 1 tick of time
  • Option duration = k_exec (number of primitive actions executed)
  • If option completes: duration = len(option.actions)
  • If terminated early: duration < len(option.actions)
# This option always executes 3 steps = 3 ticks
option = Option(actions=[0, 1, 0], name="three-steps")

# If it completes: duration = 3 ticks
# If terminated after 2 steps: duration = 2 ticks

SMDP Discounting

Standard MDP: γ^{1} per step | SMDP: γ^{k} where k = option duration

# Standard MDP discounting (each primitive step)
mdp_value = r1 + γ¹·r2 + γ²·r3 + γ³·r4

# SMDP discounting with options of lengths [3, 2, 4]
smdp_value = r1 + γ³·r2 + γ⁵·r3 + γ⁹·r4
#                   ↑      ↑       ↑
#                   3    3+2    3+2+4

🔧 Interfaces

Index Interface (Recommended for RL)

# Exposes Discrete(max_options) action space
smdp_env = SMDPfier(
    env,
    options_provider=options,
    action_interface="index",
    max_options=len(options)
)

# Use integer indices
action = 1  # Select second option
obs, reward, term, trunc, info = smdp_env.step(action)

Direct Interface (Intuitive)

# Pass Option objects directly
smdp_env = SMDPfier(
    env, 
    options_provider=options,
    action_interface="direct"
)

# Use Option objects
option = options[1]
obs, reward, term, trunc, info = smdp_env.step(option)

📚 Documentation

Topic Description
API Reference Complete API documentation and examples
Durations Guide Understanding duration = k_exec and SMDP discounting
Index vs Direct Choosing the right interface for your use case
Masking & Precheck Action constraints and validation
Error Handling Comprehensive error context and debugging
FAQ Common questions and gotchas
Migration from 0.1.x Upgrading to v0.2.0 simplified semantics

🔍 SMDP Info Payload

Every step returns rich metadata in info["smdp"]:

{
    "option": {
        "id": "abc123...",           # Stable hash-based ID
        "name": "left-right-left",   # Human-readable name
        "len": 3,                    # Number of primitive actions  
        "meta": {}                   # User metadata
    },
    "k_exec": 3,                     # Primitive steps executed
    "duration": 3,                   # Duration in ticks (= k_exec)
    "rewards": [1.0, 1.0, 1.0],     # Per-step rewards
    "terminated_early": False,       # Whether episode ended during option
    "action_mask": [1, 1, 0],       # Available option indices (index interface)
    "num_dropped": 0                 # Options dropped due to overflow (index interface)
}

🎲 Built-in Defaults

Option Generators

from smdpfier.defaults.options import RandomStaticLen, RandomVarLen

# Fixed-length random options
RandomStaticLen(length=3, action_space_size=4, num_options=10)

# Variable-length random options  
RandomVarLen(min_length=2, max_length=5, action_space_size=4, num_options=8)

Reward Aggregation

from smdpfier.defaults import sum_rewards, mean_rewards, discounted_sum

# Sum all per-step rewards (default)
reward_agg=sum_rewards

# Average per-step rewards
reward_agg=mean_rewards

# Discount per-step rewards with γ=0.99
reward_agg=discounted_sum(gamma=0.99)

🔧 Installation

pip install smdpfier

Development Install:

git clone https://github.com/smdpfier/smdpfier.git
cd smdpfier
pip install -e ".[dev,docs]"

🧪 Examples

See the examples/ directory:

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

📄 License

MIT License - see LICENSE for details.

📚 Citation

If you use SMDPfier in your research, please cite:

@software{smdpfier2024,
  title = {SMDPfier: SMDP-level behavior for Gymnasium environments},
  author = {Erel A. Shtossel, Gal A. Kaminka},
  url = {https://github.com/smdpfier/smdpfier},
  year = {2024}
}

📖 Documentation | 🐛 Issues | 💬 Discussions

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

smdpfier-0.1.0.tar.gz (727.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

smdpfier-0.1.0-py3-none-any.whl (19.8 kB view details)

Uploaded Python 3

File details

Details for the file smdpfier-0.1.0.tar.gz.

File metadata

  • Download URL: smdpfier-0.1.0.tar.gz
  • Upload date:
  • Size: 727.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for smdpfier-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5cbbc6a3be12666d5d322a43ea76aeb754def0a49e6ab56cf68c2e6b706f85c6
MD5 7519a2a1ceca107eba6a2b364a5e8d01
BLAKE2b-256 590722a51fc50f77b330fa10aa0003e8aba9ccf3a116a373fbda589e912f7395

See more details on using hashes here.

Provenance

The following attestation bundles were made for smdpfier-0.1.0.tar.gz:

Publisher: publish.yml on erelon/SMDPfier

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file smdpfier-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: smdpfier-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 19.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for smdpfier-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 31563574f474c71fbff1e183bb3df8ecfb99f2b6647844ffed44487223af307d
MD5 29fad1a55e66add1e92cefb7f11bbb8a
BLAKE2b-256 f97dd5204c3a37a9d8d832147e921dd4c23e96441e7aec4e3e7144d64065ddd1

See more details on using hashes here.

Provenance

The following attestation bundles were made for smdpfier-0.1.0-py3-none-any.whl:

Publisher: publish.yml on erelon/SMDPfier

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page