feat(config): introduce polymorphic architecture configs
centralized/decentralized
This commit is contained in:
parent
157e061f86
commit
82dfcabede
6 changed files with 163 additions and 32 deletions
21
configs/architecture/centralized.yaml
Normal file
21
configs/architecture/centralized.yaml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Centralized Actor-Critic Architecture
|
||||
# Baseline configuration with a single global sensor and motor.
|
||||
|
||||
# Default values are defined in CentralizedConfig dataclass.
|
||||
# Use this configuration for standard PPO experiments.
|
||||
|
||||
sensor:
|
||||
hidden_dims: [64, 64]
|
||||
activation: "tanh"
|
||||
|
||||
motor:
|
||||
hidden_dims: []
|
||||
activation: "tanh"
|
||||
|
||||
feature_extractor:
|
||||
hidden_dims: [64, 64]
|
||||
activation: "tanh"
|
||||
|
||||
critic:
|
||||
hidden_dims: []
|
||||
activation: "tanh"
|
||||
31
configs/architecture/decentralized.yaml
Normal file
31
configs/architecture/decentralized.yaml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Decentralized Actor Architecture (NerveNet-MLP variant)
|
||||
# Multi-agent/distributed configuration using local sensors, propagators, and motors.
|
||||
|
||||
# Default values are defined in DecentralizedConfig dataclass.
|
||||
# Use this configuration for decentralized execution experiments.
|
||||
|
||||
sensor:
|
||||
hidden_dims: [64, 64]
|
||||
activation: "tanh"
|
||||
|
||||
propagator:
|
||||
hidden_dims: [64, 64]
|
||||
activation: "tanh"
|
||||
|
||||
motor:
|
||||
hidden_dims: []
|
||||
activation: "tanh"
|
||||
|
||||
feature_extractor:
|
||||
hidden_dims: [64, 64]
|
||||
activation: "tanh"
|
||||
|
||||
critic:
|
||||
hidden_dims: []
|
||||
activation: "tanh"
|
||||
|
||||
# Synchronous message-passing rounds per control step
|
||||
message_passing_steps: 1
|
||||
|
||||
# Connectivity topology (e.g., ring, fully_connected)
|
||||
topology_type: "ring"
|
||||
84
src/brittle_star_project/configs/config_architecture.py
Normal file
84
src/brittle_star_project/configs/config_architecture.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
|
||||
@dataclass
|
||||
class LayerConfig:
|
||||
hidden_dims: List[int] = field(default_factory=lambda: [64, 64])
|
||||
activation: str = "tanh"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ArchitectureConfig:
|
||||
"""Base class for actor-critic network configurations.
|
||||
|
||||
Both centralized and decentralized architectures share a centralized critic
|
||||
composed of a feature extractor followed by a shallow output layer.
|
||||
|
||||
See docs/design/actor-critic.md for the full design rationale.
|
||||
"""
|
||||
|
||||
# Input network: maps global observation to a hidden representation.
|
||||
feature_extractor: LayerConfig = field(
|
||||
default_factory=lambda: LayerConfig(hidden_dims=[64, 64], activation="tanh")
|
||||
)
|
||||
# Output network: maps the hidden representation to a scalar value estimate.
|
||||
critic: LayerConfig = field(
|
||||
default_factory=lambda: LayerConfig(hidden_dims=[], activation="tanh")
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CentralizedConfig(ArchitectureConfig):
|
||||
"""Centralized actor-critic architecture (baseline).
|
||||
|
||||
The actor is a single global policy composed of a sensor (input network)
|
||||
and a motor (output network). The sensor receives the full concatenated
|
||||
global observation; the motor projects the hidden state to all joint actions.
|
||||
|
||||
See docs/design/actor-critic.md for the full design rationale.
|
||||
"""
|
||||
|
||||
# Input network: maps the global observation to a hidden state.
|
||||
sensor: LayerConfig = field(
|
||||
default_factory=lambda: LayerConfig(hidden_dims=[64, 64], activation="tanh")
|
||||
)
|
||||
# Output network: projects hidden state to (mean, log_std) over all joints.
|
||||
motor: LayerConfig = field(
|
||||
default_factory=lambda: LayerConfig(hidden_dims=[], activation="tanh")
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DecentralizedConfig(ArchitectureConfig):
|
||||
"""Decentralized actor architecture (NerveNet-MLP variant).
|
||||
|
||||
Each node runs a local sensor, exchanges messages with neighbours via a
|
||||
propagator for a fixed number of steps, and then a local motor produces
|
||||
the joint offset for that node only.
|
||||
|
||||
The critic remains centralized (shared with the base class): it receives the
|
||||
full concatenated global observation and outputs a single scalar.
|
||||
|
||||
See docs/design/actor-critic.md and docs/design/communication.md for the
|
||||
full design rationale.
|
||||
"""
|
||||
|
||||
# Input network: local observation -> initial hidden state per node.
|
||||
sensor: LayerConfig = field(
|
||||
default_factory=lambda: LayerConfig(hidden_dims=[64, 64], activation="tanh")
|
||||
)
|
||||
# Message-passing network: aggregates neighbour messages and updates hidden state.
|
||||
propagator: LayerConfig = field(
|
||||
default_factory=lambda: LayerConfig(hidden_dims=[64, 64], activation="tanh")
|
||||
)
|
||||
# Output network: final hidden state -> joint offset for this node only.
|
||||
motor: LayerConfig = field(
|
||||
default_factory=lambda: LayerConfig(hidden_dims=[], activation="tanh")
|
||||
)
|
||||
|
||||
# Number of synchronous message-passing rounds per control step.
|
||||
message_passing_steps: int = 1
|
||||
# Graph topology used for neighbour connections.
|
||||
# Supported values: "ring", "fully_connected".
|
||||
topology_type: str = "ring"
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class LayerConfig:
|
||||
hidden_dims: List[int] = field(default_factory=lambda: [64, 64])
|
||||
activation: str = "relu"
|
||||
|
||||
|
||||
@dataclass
|
||||
class MLPConfig:
|
||||
actor: LayerConfig = field(default_factory=LayerConfig)
|
||||
critic: LayerConfig = field(default_factory=LayerConfig)
|
||||
sensor: LayerConfig = field(default_factory=LayerConfig)
|
||||
feature_extractor: LayerConfig = field(default_factory=LayerConfig)
|
||||
|
||||
|
||||
@dataclass
|
||||
class NetworksConfig:
|
||||
mlp: MLPConfig = field(default_factory=MLPConfig)
|
||||
# Future placeholder for message_passing
|
||||
# message_passing: Optional[MessagePassingConfig] = None
|
||||
|
|
@ -3,16 +3,23 @@ from dataclasses import dataclass, field
|
|||
from experiment_logger.config_logger import LoggingConfig
|
||||
from brittle_star_project.configs.config_experiment import ExperimentConfig
|
||||
from brittle_star_project.configs.config_ppo import PPOConfig
|
||||
from brittle_star_project.configs.config_networks import NetworksConfig
|
||||
from brittle_star_project.configs.config_architecture import ArchitectureConfig, CentralizedConfig
|
||||
from brittle_star_project.environment.env_config import MorphologyConfig, ArenaConfig, EnvConfig
|
||||
|
||||
|
||||
@dataclass
|
||||
class BrittleStarConfig:
|
||||
"""Root configuration for a brittle star training run.
|
||||
|
||||
Composed of strictly separated sub-configs. Each sub-config can be swapped
|
||||
independently via CLI or a different YAML file. See configs/README.md.
|
||||
"""
|
||||
|
||||
experiment: ExperimentConfig = field(default_factory=ExperimentConfig)
|
||||
logging: LoggingConfig = field(default_factory=LoggingConfig)
|
||||
ppo: PPOConfig = field(default_factory=PPOConfig)
|
||||
networks: NetworksConfig = field(default_factory=NetworksConfig)
|
||||
# Default to centralized; swap with architecture=decentralized on the CLI.
|
||||
architecture: ArchitectureConfig = field(default_factory=CentralizedConfig)
|
||||
morphology: MorphologyConfig = field(default_factory=MorphologyConfig)
|
||||
arena: ArenaConfig = field(default_factory=ArenaConfig)
|
||||
environment: EnvConfig = field(default_factory=EnvConfig)
|
||||
|
|
|
|||
|
|
@ -3,25 +3,36 @@ from hydra.core.config_store import ConfigStore
|
|||
from experiment_logger.config_logger import LoggingConfig
|
||||
from brittle_star_project.configs.config_experiment import ExperimentConfig
|
||||
from brittle_star_project.configs.config_ppo import PPOConfig
|
||||
from brittle_star_project.configs.config_networks import NetworksConfig, MLPConfig, LayerConfig
|
||||
from brittle_star_project.configs.config_architecture import (
|
||||
CentralizedConfig,
|
||||
DecentralizedConfig,
|
||||
)
|
||||
from brittle_star_project.environment.env_config import MorphologyConfig, ArenaConfig, EnvConfig
|
||||
from brittle_star_project.configs.main_config import BrittleStarConfig
|
||||
|
||||
|
||||
def register_configs():
|
||||
"""Register dataclasses with Hydra's ConfigStore."""
|
||||
def register_configs() -> None:
|
||||
"""Register all dataclasses with Hydra's ConfigStore.
|
||||
|
||||
This must be called before hydra.main() processes the config, ensuring
|
||||
every structured config is validated against its Python schema. Typos in
|
||||
YAML keys will raise ConfigAttributeError at startup.
|
||||
"""
|
||||
cs = ConfigStore.instance()
|
||||
|
||||
# Store the main config schema
|
||||
# Root schema
|
||||
cs.store(name="brittle_star_config", node=BrittleStarConfig)
|
||||
|
||||
# Store individual structured configs for validation
|
||||
# Sub-config groups — each group corresponds to a configs/ subdirectory.
|
||||
cs.store(group="experiment", name="base_experiment", node=ExperimentConfig)
|
||||
cs.store(group="logging", name="base_logging", node=LoggingConfig)
|
||||
cs.store(group="ppo", name="base_ppo", node=PPOConfig)
|
||||
cs.store(group="networks", name="base_networks", node=NetworksConfig)
|
||||
|
||||
# Environment configs (reusing existing environment config objects)
|
||||
# Architecture variants — swap via CLI: architecture=decentralized
|
||||
cs.store(group="architecture", name="centralized", node=CentralizedConfig)
|
||||
cs.store(group="architecture", name="decentralized", node=DecentralizedConfig)
|
||||
|
||||
# Environment configs
|
||||
cs.store(group="morphology", name="base_morphology", node=MorphologyConfig)
|
||||
cs.store(group="arena", name="base_arena", node=ArenaConfig)
|
||||
cs.store(group="environment", name="base_environment", node=EnvConfig)
|
||||
|
|
|
|||
Reference in a new issue