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 experiment_logger.config_logger import LoggingConfig
|
||||||
from brittle_star_project.configs.config_experiment import ExperimentConfig
|
from brittle_star_project.configs.config_experiment import ExperimentConfig
|
||||||
from brittle_star_project.configs.config_ppo import PPOConfig
|
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
|
from brittle_star_project.environment.env_config import MorphologyConfig, ArenaConfig, EnvConfig
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BrittleStarConfig:
|
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)
|
experiment: ExperimentConfig = field(default_factory=ExperimentConfig)
|
||||||
logging: LoggingConfig = field(default_factory=LoggingConfig)
|
logging: LoggingConfig = field(default_factory=LoggingConfig)
|
||||||
ppo: PPOConfig = field(default_factory=PPOConfig)
|
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)
|
morphology: MorphologyConfig = field(default_factory=MorphologyConfig)
|
||||||
arena: ArenaConfig = field(default_factory=ArenaConfig)
|
arena: ArenaConfig = field(default_factory=ArenaConfig)
|
||||||
environment: EnvConfig = field(default_factory=EnvConfig)
|
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 experiment_logger.config_logger import LoggingConfig
|
||||||
from brittle_star_project.configs.config_experiment import ExperimentConfig
|
from brittle_star_project.configs.config_experiment import ExperimentConfig
|
||||||
from brittle_star_project.configs.config_ppo import PPOConfig
|
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.environment.env_config import MorphologyConfig, ArenaConfig, EnvConfig
|
||||||
from brittle_star_project.configs.main_config import BrittleStarConfig
|
from brittle_star_project.configs.main_config import BrittleStarConfig
|
||||||
|
|
||||||
|
|
||||||
def register_configs():
|
def register_configs() -> None:
|
||||||
"""Register dataclasses with Hydra's ConfigStore."""
|
"""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()
|
cs = ConfigStore.instance()
|
||||||
|
|
||||||
# Store the main config schema
|
# Root schema
|
||||||
cs.store(name="brittle_star_config", node=BrittleStarConfig)
|
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="experiment", name="base_experiment", node=ExperimentConfig)
|
||||||
cs.store(group="logging", name="base_logging", node=LoggingConfig)
|
cs.store(group="logging", name="base_logging", node=LoggingConfig)
|
||||||
cs.store(group="ppo", name="base_ppo", node=PPOConfig)
|
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="morphology", name="base_morphology", node=MorphologyConfig)
|
||||||
cs.store(group="arena", name="base_arena", node=ArenaConfig)
|
cs.store(group="arena", name="base_arena", node=ArenaConfig)
|
||||||
cs.store(group="environment", name="base_environment", node=EnvConfig)
|
cs.store(group="environment", name="base_environment", node=EnvConfig)
|
||||||
|
|
|
||||||
Reference in a new issue