Skip to content

API Reference

AppConfig

Bases: BaseImmutableConfig

The master configuration schema for the entire simulation application. Aggregates all sub-configurations and validates cross-domain logical consistency.

Source code in src/afl_sim/config.py
class AppConfig(BaseImmutableConfig):
    """
    The master configuration schema for the entire simulation application.
    Aggregates all sub-configurations and validates cross-domain logical consistency.
    """

    comm_strategy: CommStrategyConfig = Field(default_factory=AsyncStrategy)
    mem_strategy: MemStrategyConfig = Field(default_factory=MemStrategyConfig)

    data: DataConfig = Field(default_factory=DataConfig)
    model: ModelConfig = Field(default_factory=ModelConfig)
    simulation: SimulationConfig = Field(default_factory=SimulationConfig)
    evaluation: EvaluationConfig = Field(default_factory=EvaluationConfig)
    optimization: OptimizationConfig = Field(default_factory=OptimizationConfig)
    checkpoints: CheckpointConfig = Field(default_factory=CheckpointConfig)
    visualization: VisualizationConfig = Field(default_factory=VisualizationConfig)

    @model_validator(mode="after")
    def check_logical_consistency(self) -> "AppConfig":
        """
        Validates that the communication strategy does not request more clients
        than are available in the simulation pool.

        Returns:
            AppConfig: The validated configuration object.

        Raises:
            ValueError: If synchronous sample size exceeds total clients.
        """
        if (
            self.comm_strategy.type == "sync"
            and self.comm_strategy.sample_size > self.simulation.num_clients
        ):
            raise ValueError(
                f"Config Error: Sample size ({self.comm_strategy.sample_size}) cannot "
                f"exceed total clients ({self.simulation.num_clients})."
            )
        return self

    @model_validator(mode="after")
    def check_model_compatibility(self) -> "AppConfig":
        """
        Validates that the selected model architecture can accept the number
        of input channels provided by the selected dataset.

        Returns:
            AppConfig: The validated configuration object.

        Raises:
            ValueError: If a strict channel mismatch occurs.
        """
        dataset = self.data.dataset
        model = self.model.model_name

        if model.required_channels and dataset.num_channels != model.required_channels:
            raise ValueError(
                f"Config Error: '{model}' requires {model.required_channels} channel(s), "
                f"but '{dataset}' has {dataset.num_channels}. Choose a different model."
            )

        return self

    @model_validator(mode="after")
    def check_checkpoint_interval(self) -> "AppConfig":
        """
        Checks if the checkpoint interval exceeds or equals the simulation timeout,
        warning the user that intermediate checkpoints will not be saved.

        Returns:
            AppConfig: The validated configuration object.
        """
        interval = self.checkpoints.interval_seconds
        timeout = self.simulation.timeout_seconds

        if interval >= timeout:  # pragma: no branch
            logger.warning(
                f"Config Warning: Checkpoint interval '{interval}' is equal or greater than the "
                f"simulation timeout '{timeout}'. The simulation will save a final checkpoint "
                f"upon termination or interruption, and no intermediate checkpoints."
            )

        return self

    @model_validator(mode="after")
    def sanitize_visualization_config(self) -> "AppConfig":
        """
        Disables visualizations automatically if the client count exceeds a readability
        threshold (150 clients) to prevent resource exhaustion and unreadable plots.

        Returns:
            AppConfig: The sanitized configuration object with updated visualization flags.
        """
        limit = 150  # Threshold for readable plots

        disable_split = (
            self.visualization.visualize_data_split
            and self.simulation.num_clients > limit
        )
        disable_arrivals = (
            self.visualization.visualize_client_arrivals
            and self.simulation.num_clients > limit
        )

        if not (disable_split or disable_arrivals):
            return self

        if disable_split:  # pragma: no branch
            logger.warning(
                f"Config Warning: Too many clients ({self.simulation.num_clients}) for "
                "data split visualization. Disabling to prevent unreadable plot."
            )
        if disable_arrivals:  # pragma: no branch
            logger.warning(
                f"Config Warning: Too many clients ({self.simulation.num_clients}) for "
                "arrival visualization. Disabling to prevent unreadable plot."
            )

        new_viz_config = self.visualization.model_copy(
            update={
                "visualize_data_split": False
                if disable_split
                else self.visualization.visualize_data_split,
                "visualize_client_arrivals": False
                if disable_arrivals
                else self.visualization.visualize_client_arrivals,
            }
        )

        object.__setattr__(self, "visualization", new_viz_config)

        return self

    @model_validator(mode="after")
    def check_batch_size_validity(self) -> "AppConfig":
        """
        Validates that training and evaluation batch sizes do not exceed
        their respective total dataset sizes.

        Returns:
            AppConfig: The validated configuration object.

        Raises:
            ValueError: If a configured batch size exceeds the available dataset size.
        """
        train_size = self.data.dataset.train_size
        test_size = self.data.dataset.test_size
        batch_size = self.optimization.batch_size
        batch_size_eval = self.evaluation.batch_size

        if batch_size >= train_size:
            raise ValueError(
                f"Config Error: Batch size ({batch_size}) cannot be equal to or exceed "
                f"dataset size ({train_size}) for {self.data.dataset.name}."
            )

        if batch_size_eval >= test_size:
            raise ValueError(
                f"Config Error: Evaluation batch size ({batch_size_eval}) cannot be equal to or exceed "
                f"test dataset size ({test_size}) for {self.data.dataset.name}."
            )
        return self

check_batch_size_validity()

Validates that training and evaluation batch sizes do not exceed their respective total dataset sizes.

Returns:

Name Type Description
AppConfig AppConfig

The validated configuration object.

Raises:

Type Description
ValueError

If a configured batch size exceeds the available dataset size.

Source code in src/afl_sim/config.py
@model_validator(mode="after")
def check_batch_size_validity(self) -> "AppConfig":
    """
    Validates that training and evaluation batch sizes do not exceed
    their respective total dataset sizes.

    Returns:
        AppConfig: The validated configuration object.

    Raises:
        ValueError: If a configured batch size exceeds the available dataset size.
    """
    train_size = self.data.dataset.train_size
    test_size = self.data.dataset.test_size
    batch_size = self.optimization.batch_size
    batch_size_eval = self.evaluation.batch_size

    if batch_size >= train_size:
        raise ValueError(
            f"Config Error: Batch size ({batch_size}) cannot be equal to or exceed "
            f"dataset size ({train_size}) for {self.data.dataset.name}."
        )

    if batch_size_eval >= test_size:
        raise ValueError(
            f"Config Error: Evaluation batch size ({batch_size_eval}) cannot be equal to or exceed "
            f"test dataset size ({test_size}) for {self.data.dataset.name}."
        )
    return self

check_checkpoint_interval()

Checks if the checkpoint interval exceeds or equals the simulation timeout, warning the user that intermediate checkpoints will not be saved.

Returns:

Name Type Description
AppConfig AppConfig

The validated configuration object.

Source code in src/afl_sim/config.py
@model_validator(mode="after")
def check_checkpoint_interval(self) -> "AppConfig":
    """
    Checks if the checkpoint interval exceeds or equals the simulation timeout,
    warning the user that intermediate checkpoints will not be saved.

    Returns:
        AppConfig: The validated configuration object.
    """
    interval = self.checkpoints.interval_seconds
    timeout = self.simulation.timeout_seconds

    if interval >= timeout:  # pragma: no branch
        logger.warning(
            f"Config Warning: Checkpoint interval '{interval}' is equal or greater than the "
            f"simulation timeout '{timeout}'. The simulation will save a final checkpoint "
            f"upon termination or interruption, and no intermediate checkpoints."
        )

    return self

check_logical_consistency()

Validates that the communication strategy does not request more clients than are available in the simulation pool.

Returns:

Name Type Description
AppConfig AppConfig

The validated configuration object.

Raises:

Type Description
ValueError

If synchronous sample size exceeds total clients.

Source code in src/afl_sim/config.py
@model_validator(mode="after")
def check_logical_consistency(self) -> "AppConfig":
    """
    Validates that the communication strategy does not request more clients
    than are available in the simulation pool.

    Returns:
        AppConfig: The validated configuration object.

    Raises:
        ValueError: If synchronous sample size exceeds total clients.
    """
    if (
        self.comm_strategy.type == "sync"
        and self.comm_strategy.sample_size > self.simulation.num_clients
    ):
        raise ValueError(
            f"Config Error: Sample size ({self.comm_strategy.sample_size}) cannot "
            f"exceed total clients ({self.simulation.num_clients})."
        )
    return self

check_model_compatibility()

Validates that the selected model architecture can accept the number of input channels provided by the selected dataset.

Returns:

Name Type Description
AppConfig AppConfig

The validated configuration object.

Raises:

Type Description
ValueError

If a strict channel mismatch occurs.

Source code in src/afl_sim/config.py
@model_validator(mode="after")
def check_model_compatibility(self) -> "AppConfig":
    """
    Validates that the selected model architecture can accept the number
    of input channels provided by the selected dataset.

    Returns:
        AppConfig: The validated configuration object.

    Raises:
        ValueError: If a strict channel mismatch occurs.
    """
    dataset = self.data.dataset
    model = self.model.model_name

    if model.required_channels and dataset.num_channels != model.required_channels:
        raise ValueError(
            f"Config Error: '{model}' requires {model.required_channels} channel(s), "
            f"but '{dataset}' has {dataset.num_channels}. Choose a different model."
        )

    return self

sanitize_visualization_config()

Disables visualizations automatically if the client count exceeds a readability threshold (150 clients) to prevent resource exhaustion and unreadable plots.

Returns:

Name Type Description
AppConfig AppConfig

The sanitized configuration object with updated visualization flags.

Source code in src/afl_sim/config.py
@model_validator(mode="after")
def sanitize_visualization_config(self) -> "AppConfig":
    """
    Disables visualizations automatically if the client count exceeds a readability
    threshold (150 clients) to prevent resource exhaustion and unreadable plots.

    Returns:
        AppConfig: The sanitized configuration object with updated visualization flags.
    """
    limit = 150  # Threshold for readable plots

    disable_split = (
        self.visualization.visualize_data_split
        and self.simulation.num_clients > limit
    )
    disable_arrivals = (
        self.visualization.visualize_client_arrivals
        and self.simulation.num_clients > limit
    )

    if not (disable_split or disable_arrivals):
        return self

    if disable_split:  # pragma: no branch
        logger.warning(
            f"Config Warning: Too many clients ({self.simulation.num_clients}) for "
            "data split visualization. Disabling to prevent unreadable plot."
        )
    if disable_arrivals:  # pragma: no branch
        logger.warning(
            f"Config Warning: Too many clients ({self.simulation.num_clients}) for "
            "arrival visualization. Disabling to prevent unreadable plot."
        )

    new_viz_config = self.visualization.model_copy(
        update={
            "visualize_data_split": False
            if disable_split
            else self.visualization.visualize_data_split,
            "visualize_client_arrivals": False
            if disable_arrivals
            else self.visualization.visualize_client_arrivals,
        }
    )

    object.__setattr__(self, "visualization", new_viz_config)

    return self

AsyncStrategy

Bases: BaseImmutableConfig

Configuration for asynchronous federated learning strategies.

Source code in src/afl_sim/config.py
class AsyncStrategy(BaseImmutableConfig):
    """Configuration for asynchronous federated learning strategies."""

    type: Literal["async"] = "async"
    buffer_size: int = Field(
        default=3,
        gt=0,
        description="Number of client updates that triggers a global model update.",
    )

    @property
    def agg_target(self) -> int:
        """
        Retrieves the target number of client updates required for a global aggregation.

        Returns:
            int: The configured buffer size limit.
        """
        return self.buffer_size

agg_target property

Retrieves the target number of client updates required for a global aggregation.

Returns:

Name Type Description
int int

The configured buffer size limit.

CheckpointConfig

Bases: BaseImmutableConfig

Configuration for managing state serialization and disk I/O. Controls both interval-based heavy checkpoints and best-model artifacts.

Source code in src/afl_sim/config.py
class CheckpointConfig(BaseImmutableConfig):
    """
    Configuration for managing state serialization and disk I/O.
    Controls both interval-based heavy checkpoints and best-model artifacts.
    """

    interval_seconds: float = Field(
        default=400.0,
        gt=0,
        description="The interval (in wall-clock seconds) at which the simulator saves a resumable checkpoint.",
    )
    keep_best: bool = Field(
        default=False,
        description="If set to `True`, the simulator continuously saves a separate copy of the global model that achieved the highest accuracy on the test set.",
    )

DataConfig

Bases: BaseImmutableConfig

Configuration for dataset selection and distributed partitioning.

Source code in src/afl_sim/config.py
class DataConfig(BaseImmutableConfig):
    """Configuration for dataset selection and distributed partitioning."""

    dataset: DatasetType = Field(
        default=DatasetType.MNIST,
        description="The target dataset for the simulation.",
    )
    dirichlet_alpha: float = Field(
        default=0.1,
        gt=0.0,
        description="Dirichlet distribution parameter.",
    )
    split_seed: int = Field(
        default=42,
        ge=0,
        description="The random seed ensuring reproducibility during the dataset partitioning process.",
    )

DatasetType

Bases: StrEnum

Enumeration of supported federated learning datasets.

Attributes:

Name Type Description
MNIST str

The MNIST dataset of handwritten digits.

FASHION_MNIST str

The Fashion-MNIST dataset of clothing articles.

CIFAR10 str

The CIFAR-10 dataset of 10 object classes.

CIFAR100 str

The CIFAR-100 dataset of 100 object classes.

Source code in src/afl_sim/enums.py
class DatasetType(StrEnum):
    """
    Enumeration of supported federated learning datasets.

    Attributes:
        MNIST (str): The MNIST dataset of handwritten digits.
        FASHION_MNIST (str): The Fashion-MNIST dataset of clothing articles.
        CIFAR10 (str): The CIFAR-10 dataset of 10 object classes.
        CIFAR100 (str): The CIFAR-100 dataset of 100 object classes.
    """

    MNIST = "mnist"
    FASHION_MNIST = "fashion_mnist"
    CIFAR10 = "cifar10"
    CIFAR100 = "cifar100"

    @property
    def source(self) -> str:
        """
        Retrieves the source library mapping for the dataset.

        Returns:
            str: The name of the upstream library (e.g., "torchvision").
        """
        match self:
            case (
                DatasetType.MNIST
                | DatasetType.FASHION_MNIST
                | DatasetType.CIFAR10
                | DatasetType.CIFAR100
            ):
                return "torchvision"

    @property
    def source_name(self) -> str:
        """
        Retrieves the exact dataset class name used by the source library.

        Returns:
            str: The string identifier for the source dataset class.
        """
        match self:
            case DatasetType.MNIST:
                return "MNIST"
            case DatasetType.FASHION_MNIST:
                return "FashionMNIST"
            case DatasetType.CIFAR10:
                return "CIFAR10"
            case DatasetType.CIFAR100:
                return "CIFAR100"

    @property
    def train_size(self) -> int:
        """
        Retrieves the total number of samples in the raw training split.

        Returns:
            int: The training dataset size.
        """
        match self:
            case DatasetType.MNIST | DatasetType.FASHION_MNIST:
                return 60000
            case DatasetType.CIFAR10 | DatasetType.CIFAR100:
                return 50000

    @property
    def test_size(self) -> int:
        """
        Retrieves the total number of samples in the raw evaluation split.

        Returns:
            int: The evaluation dataset size.
        """
        return 10000

    @property
    def num_classes(self) -> int:
        """
        Retrieves the total number of target classes/labels in the dataset.

        Returns:
            int: The number of classes.
        """
        match self:
            case DatasetType.CIFAR100:
                return 100
            case _:
                return 10

    @property
    def num_channels(self) -> int:
        """
        Retrieves the number of color channels in the dataset images.

        Returns:
            int: 1 for grayscale, 3 for RGB.
        """
        match self:
            case DatasetType.MNIST | DatasetType.FASHION_MNIST:
                return 1
            case _:
                return 3

    @property
    def image_size(self) -> int:
        """
        Retrieves the pixel height and width of the images (assumes square aspect ratio).

        Returns:
            int: The image dimension in pixels.
        """
        match self:
            case DatasetType.MNIST | DatasetType.FASHION_MNIST:
                return 28
            case _:
                return 32

    @property
    def mean(self) -> tuple[float] | tuple[float, float, float]:
        """
        Retrieves the channel-wise mean values for dataset normalization.

        Returns:
            tuple[float] | tuple[float, float, float]: A tuple of means for each channel.
        """
        match self:
            case DatasetType.MNIST:
                return (0.1307,)
            case DatasetType.FASHION_MNIST:
                return (0.2860,)
            case DatasetType.CIFAR10:
                return (0.4914, 0.4822, 0.4465)
            case DatasetType.CIFAR100:
                return (0.5071, 0.4865, 0.4409)

    @property
    def std(self) -> tuple[float] | tuple[float, float, float]:
        """
        Retrieves the channel-wise standard deviation values for dataset normalization.

        Returns:
            tuple[float] | tuple[float, float, float]: A tuple of standard deviations for each channel.
        """
        match self:
            case DatasetType.MNIST:
                return (0.3081,)
            case DatasetType.FASHION_MNIST:
                return (0.3530,)
            case DatasetType.CIFAR10:
                return (0.2470, 0.2435, 0.2616)
            case DatasetType.CIFAR100:
                return (0.2673, 0.2564, 0.2762)

    @property
    def apply_crop_transform(self) -> bool:
        """Determines whether random cropping should be applied during training.

        Returns:
            bool: True if random cropping is enabled, False otherwise.
        """
        match self:
            case DatasetType.CIFAR10 | DatasetType.CIFAR100:
                return True
            case _:
                return False

    @property
    def apply_horizontal_flip_transform(self) -> bool:
        """
        Determines whether random horizontal flipping should be applied during training.

        Returns:
            bool: True if horizontal flipping is enabled, False otherwise.
        """
        match self:
            case DatasetType.CIFAR10 | DatasetType.CIFAR100:
                return True
            case _:
                return False

apply_crop_transform property

Determines whether random cropping should be applied during training.

Returns:

Name Type Description
bool bool

True if random cropping is enabled, False otherwise.

apply_horizontal_flip_transform property

Determines whether random horizontal flipping should be applied during training.

Returns:

Name Type Description
bool bool

True if horizontal flipping is enabled, False otherwise.

image_size property

Retrieves the pixel height and width of the images (assumes square aspect ratio).

Returns:

Name Type Description
int int

The image dimension in pixels.

mean property

Retrieves the channel-wise mean values for dataset normalization.

Returns:

Type Description
tuple[float] | tuple[float, float, float]

tuple[float] | tuple[float, float, float]: A tuple of means for each channel.

num_channels property

Retrieves the number of color channels in the dataset images.

Returns:

Name Type Description
int int

1 for grayscale, 3 for RGB.

num_classes property

Retrieves the total number of target classes/labels in the dataset.

Returns:

Name Type Description
int int

The number of classes.

source property

Retrieves the source library mapping for the dataset.

Returns:

Name Type Description
str str

The name of the upstream library (e.g., "torchvision").

source_name property

Retrieves the exact dataset class name used by the source library.

Returns:

Name Type Description
str str

The string identifier for the source dataset class.

std property

Retrieves the channel-wise standard deviation values for dataset normalization.

Returns:

Type Description
tuple[float] | tuple[float, float, float]

tuple[float] | tuple[float, float, float]: A tuple of standard deviations for each channel.

test_size property

Retrieves the total number of samples in the raw evaluation split.

Returns:

Name Type Description
int int

The evaluation dataset size.

train_size property

Retrieves the total number of samples in the raw training split.

Returns:

Name Type Description
int int

The training dataset size.

DefaultDirs

Bases: StrEnum

Enumerates the standardized directories for storing simulation artifacts.

Attributes:

Name Type Description
DATA str

The default directory for storing generated simulation input data such as raw datasets, data splits and simualtion clocks. Defaults to "data".

OUTPUTS str

The default directory for storing execution logs, metrics logs, and runtime metadata. Defaults to "outputs".

CHECKPOINTS str

The default directory for storing resumable simulation and best model checkpoints. Defaults to "checkpoints".

Source code in src/afl_sim/enums.py
class DefaultDirs(StrEnum):
    """
    Enumerates the standardized directories for storing simulation artifacts.

    Attributes:
        DATA (str): The default directory for storing generated simulation input data such as raw datasets, data splits and simualtion clocks. Defaults to `"data"`.
        OUTPUTS (str): The default directory for storing execution logs, metrics logs, and runtime metadata. Defaults to `"outputs"`.
        CHECKPOINTS (str): The default directory for storing resumable simulation and best model checkpoints. Defaults to `"checkpoints"`.
    """

    DATA = "data"
    OUTPUTS = "outputs"
    CHECKPOINTS = "checkpoints"

DeviceType

Bases: StrEnum

Enumeration of supported hardware accelerator backends.

Attributes:

Name Type Description
CPU str

Central Processing Unit backend.

MPS str

Apple Metal Performance Shaders backend.

CUDA str

NVIDIA CUDA backend.

AUTO str

Automatically selects the best available backend.

Source code in src/afl_sim/enums.py
class DeviceType(StrEnum):
    """
    Enumeration of supported hardware accelerator backends.

    Attributes:
        CPU (str): Central Processing Unit backend.
        MPS (str): Apple Metal Performance Shaders backend.
        CUDA (str): NVIDIA CUDA backend.
        AUTO (str): Automatically selects the best available backend.
    """

    CPU = "cpu"
    MPS = "mps"
    CUDA = "cuda"
    AUTO = "auto"

EvaluationConfig

Bases: BaseImmutableConfig

Configuration for the server-side global evaluation process.

Source code in src/afl_sim/config.py
class EvaluationConfig(BaseImmutableConfig):
    """Configuration for the server-side global evaluation process."""

    batch_size: int = Field(
        default=32,
        gt=0,
        description="The number of test dataset samples processed per batch during global model evaluation (for metric generation).",
    )
    num_workers: int = Field(
        default=0,
        ge=0,
        description="The number of subprocesses used for data loading, corresponding to the PyTorch DataLoader parameter.",
    )

MemStrategyConfig

Bases: BaseImmutableConfig

Configuration defining the memory tracking behavior of clients.

Source code in src/afl_sim/config.py
class MemStrategyConfig(BaseImmutableConfig):
    """Configuration defining the memory tracking behavior of clients."""

    type: MemoryType = Field(
        default=MemoryType.DISABLED, description="Type of client memory augmentation."
    )

MemoryType

Bases: StrEnum

Enumeration of client-side memory tracking strategies.

Attributes:

Name Type Description
DISABLED str

Strategy indicating no memory tracking.

MODELS str

Strategy for tracking historical model weights.

GRADS str

Strategy for tracking historical gradients.

Source code in src/afl_sim/enums.py
class MemoryType(StrEnum):
    """
    Enumeration of client-side memory tracking strategies.

    Attributes:
        DISABLED (str): Strategy indicating no memory tracking.
        MODELS (str): Strategy for tracking historical model weights.
        GRADS (str): Strategy for tracking historical gradients.
    """

    DISABLED = "disabled"
    MODELS = "models"
    GRADS = "gradients"

    @property
    def requires_buffer_reset(self) -> bool:
        """
        Determines if the server buffer must be flushed after a global update.

        Returns:
            bool: True if the server buffer requires resetting, False if updates
                are accumulated continuously (e.g., gradient memory).
        """
        match self:
            case MemoryType.GRADS:
                return False
            case _:
                return True

    @property
    def has_memory(self) -> bool:
        """
        Determines if the selected strategy necessitates tracking client-side memory states.

        Returns:
            bool: True if memory is actively used, False if disabled.
        """
        match self:
            case MemoryType.DISABLED:
                return False
            case _:
                return True

has_memory property

Determines if the selected strategy necessitates tracking client-side memory states.

Returns:

Name Type Description
bool bool

True if memory is actively used, False if disabled.

requires_buffer_reset property

Determines if the server buffer must be flushed after a global update.

Returns:

Name Type Description
bool bool

True if the server buffer requires resetting, False if updates are accumulated continuously (e.g., gradient memory).

ModelConfig

Bases: BaseImmutableConfig

Configuration detailing the target neural network architecture.

Source code in src/afl_sim/config.py
class ModelConfig(BaseImmutableConfig):
    """Configuration detailing the target neural network architecture."""

    model_name: ModelType = Field(
        default=ModelType.CNN, description="Model architecture to use."
    )

ModelType

Bases: StrEnum

Enumeration of supported neural network architectures.

Attributes:

Name Type Description
LOG_REG str

Logistic regression architecture.

CNN str

Simple Convolutional Neural Network architecture.

RESNET18 str

ResNet-18 architecture.

Source code in src/afl_sim/enums.py
class ModelType(StrEnum):
    """
    Enumeration of supported neural network architectures.

    Attributes:
        LOG_REG (str): Logistic regression architecture.
        CNN (str): Simple Convolutional Neural Network architecture.
        RESNET18 (str): ResNet-18 architecture.
    """

    LOG_REG = "logreg"
    CNN = "cnn"
    RESNET18 = "resnet18"

    @property
    def required_channels(self) -> int | None:
        """
        Determines the strict number of input channels required by the architecture.

        Returns:
            int | None: The required integer channel count, or None if the model
                dynamically adapts to any input shape.
        """
        match self:
            case ModelType.RESNET18:
                return 3
            case _:
                return None

required_channels property

Determines the strict number of input channels required by the architecture.

Returns:

Type Description
int | None

int | None: The required integer channel count, or None if the model dynamically adapts to any input shape.

OptimizationConfig

Bases: BaseImmutableConfig

Configuration for the local client-side optimization process.

Source code in src/afl_sim/config.py
class OptimizationConfig(BaseImmutableConfig):
    """Configuration for the local client-side optimization process."""

    learning_rate: float = Field(
        default=0.1,
        gt=0.0,
        description="The step size applied during local client training.",
    )
    weight_decay: float = Field(
        default=0.0,
        ge=0.0,
        description="The L2 penalty (weight decay) applied by the PyTorch optimizer to prevent overfitting.",
    )
    num_local_steps: int = Field(
        default=100,
        gt=0,
        description="The exact number of local SGD steps (batches) a client performs before communicating with the server.",
    )
    batch_size: int = Field(
        default=32,
        gt=0,
        description="The number of samples processed per local training step.",
    )

SimulationConfig

Bases: BaseImmutableConfig

Configuration for the top-level simulation environment and hardware settings.

Source code in src/afl_sim/config.py
class SimulationConfig(BaseImmutableConfig):
    """Configuration for the top-level simulation environment and hardware settings."""

    device: DeviceType = Field(
        default=DeviceType.AUTO,
        description="The hardware accelerator used for the simulation.",
    )
    num_clients: int = Field(default=10, gt=1, description="Total number of clients.")
    timeout_seconds: float = Field(
        default=300.0,
        gt=0,
        description="Simulation duration in wall-clock seconds.",
    )
    client_rate_std: float = Field(
        default=1.0,
        ge=0.0,
        description="Standard deviation of client latency.",
    )
    rate_seed: int = Field(
        default=42,
        ge=0,
        description="The random seed used to generate client arrival times and latency distributions.",
    )
    torch_seed: int = Field(
        default=42, ge=0, description="The random seed for all PyTorch operations."
    )

SyncStrategy

Bases: BaseImmutableConfig

Configuration for synchronous federated learning strategies.

Source code in src/afl_sim/config.py
class SyncStrategy(BaseImmutableConfig):
    """Configuration for synchronous federated learning strategies."""

    type: Literal["sync"] = "sync"
    sample_size: int = Field(
        default=3,
        gt=0,
        description="Number of clients sampled by the server at each round.",
    )

    @property
    def agg_target(self) -> int:
        """
        Retrieves the target number of client updates required for a global aggregation.

        Returns:
            int: The sample size per synchronous round.
        """
        return self.sample_size

agg_target property

Retrieves the target number of client updates required for a global aggregation.

Returns:

Name Type Description
int int

The sample size per synchronous round.

VisualizationConfig

Bases: BaseImmutableConfig

Configuration for creating and saving data split and client arrival visualizations.

Note: Requires matplotlib to be installed if enabled.

Source code in src/afl_sim/config.py
class VisualizationConfig(BaseImmutableConfig):
    """
    Configuration for creating and saving data split and client arrival visualizations.

    Note: Requires `matplotlib` to be installed if enabled.
    """

    visualize_data_split: bool = Field(
        default=False,
        description="Generates and saves a chart in .png format illustrating the distribution of dataset samples across the clients.",
    )

    visualize_client_arrivals: bool = Field(
        default=False,
        description="Generates and saves a timeline plot in .png format depicting the simulated arrival times and latencies of the clients.",
    )

resume_simulation(output_path, timeout=None)

Resumes an existing simulation from a previously saved output directory.

Restores the configuration, locates the appropriate datasets and checkpoints from the runtime metadata, and continues the simulation loop from the exact global index where it last stopped.

Parameters:

Name Type Description Default
output_path Path

Path to the existing run directory containing config.yaml.

required
timeout float | None

Optional override for the wall-clock timeout in seconds for this specific session.

None

Raises:

Type Description
FileNotFoundError

If the configuration or metadata files cannot be found.

ValueError

If there is an invalid parameter value.

YAMLError

If the YAML configuration is malformed.

ValidationError

If the configuration fails Pydantic validation.

PermissionError

If there are insufficient permissions to read/write directories.

OSError

If a general filesystem error occurs.

Source code in src/afl_sim/api.py
@validate_call
def resume_simulation(
    output_path: Path | str,
    timeout: float | None = None,
) -> None:
    """
    Resumes an existing simulation from a previously saved output directory.

    Restores the configuration, locates the appropriate datasets and checkpoints
    from the runtime metadata, and continues the simulation loop from the exact
    global index where it last stopped.

    Args:
        output_path (Path): Path to the existing run directory containing `config.yaml`.
        timeout (float | None, optional): Optional override for the wall-clock timeout in seconds
            for this specific session.

    Raises:
        FileNotFoundError: If the configuration or metadata files cannot be found.
        ValueError: If there is an invalid parameter value.
        YAMLError: If the YAML configuration is malformed.
        ValidationError: If the configuration fails Pydantic validation.
        PermissionError: If there are insufficient permissions to read/write directories.
        OSError: If a general filesystem error occurs.
    """
    output_path = Path(output_path)

    log_file_id = None
    try:
        log_file_id = logger.add(output_path / "run.log", rotation="10 MB", mode="a")

        config = load_config_from_run_dir_with_overrides(
            run_dir=output_path, timeout=timeout
        )
        simulation_dirs = get_simulation_dirs_from_metadata(run_dir=output_path)

        logger.info(f"Resuming Simulation from: {output_path}")

        build_and_run_simulation(
            config=config, simulation_dirs=simulation_dirs, resume=True
        )

        logger.success("Simulation resumed and terminated.")

    finally:
        if log_file_id is not None:  # pragma: no branch
            logger.remove(log_file_id)

run_simulation(config, output_dir=DefaultDirs.OUTPUTS, data_dir=DefaultDirs.DATA, checkpoint_dir=DefaultDirs.CHECKPOINTS, learning_rate=None, tag=None, dry_run=False)

Orchestrates and starts a new federated learning simulation.

This function accepts either a path to a YAML configuration file or a pre-instantiated AppConfig object. It creates a timestamped results directory, initializes the data partitions and simulation environment, and begins the run.

Parameters:

Name Type Description Default
config Path | str | AppConfig

Path to the YAML configuration file, or an AppConfig instance.

required
output_dir Path | str

Base directory for all output runs.

OUTPUTS
data_dir Path | str

Directory for saving/loading datasets, splits, and clocks.

DATA
checkpoint_dir Path | str

Base directory for saving checkpoints.

CHECKPOINTS
learning_rate float | None

Optional override for the YAML client learning rate.

None
tag str | None

Optional label appended to the run directory name.

None
dry_run bool

If True, validates the config and exits without starting.

False

Raises:

Type Description
RuntimeError

If a YAML learning rate override is requested without a YAML path.

TypeError

If the provided config is not a Path, string, or AppConfig.

FileNotFoundError

If the configuration file cannot be found.

ValueError

If there is an invalid parameter value.

YAMLError

If the YAML configuration is malformed.

ValidationError

If the configuration fails Pydantic validation.

PermissionError

If there are insufficient permissions to create directories.

OSError

If a general filesystem error occurs.

Source code in src/afl_sim/api.py
@validate_call
def run_simulation(
    config: Path | str | AppConfig,
    output_dir: Path | str = DefaultDirs.OUTPUTS,
    data_dir: Path | str = DefaultDirs.DATA,
    checkpoint_dir: Path | str = DefaultDirs.CHECKPOINTS,
    learning_rate: float | None = None,
    tag: str | None = None,
    dry_run: bool = False,
) -> None:
    """
    Orchestrates and starts a new federated learning simulation.

    This function accepts either a path to a YAML configuration file or a pre-instantiated
    AppConfig object. It creates a timestamped results directory, initializes the data
    partitions and simulation environment, and begins the run.

    Args:
        config (Path | str | AppConfig): Path to the YAML configuration file, or an AppConfig instance.
        output_dir (Path | str, optional): Base directory for all output runs.
        data_dir (Path | str, optional): Directory for saving/loading datasets, splits, and clocks.
        checkpoint_dir (Path | str, optional): Base directory for saving checkpoints.
        learning_rate (float | None, optional): Optional override for the YAML client learning rate.
        tag (str | None, optional): Optional label appended to the run directory name.
        dry_run (bool, optional): If True, validates the config and exits without starting.

    Raises:
        RuntimeError: If a YAML learning rate override is requested without a YAML path.
        TypeError: If the provided config is not a Path, string, or AppConfig.
        FileNotFoundError: If the configuration file cannot be found.
        ValueError: If there is an invalid parameter value.
        YAMLError: If the YAML configuration is malformed.
        ValidationError: If the configuration fails Pydantic validation.
        PermissionError: If there are insufficient permissions to create directories.
        OSError: If a general filesystem error occurs.
    """
    output_dir = Path(output_dir)
    data_dir = Path(data_dir)
    checkpoint_dir = Path(checkpoint_dir)

    if isinstance(config, (Path, str)):
        resolved_config = load_config_with_overrides(
            config_path=Path(config),
            learning_rate=learning_rate,
        )
    else:
        resolved_config = config
        if (
            learning_rate is not None
            and learning_rate != config.optimization.learning_rate
        ):
            raise RuntimeError(
                "Config Error: Learning rate override only allowed "
                "for YAML configs. To modify the learning rate, "
                "edit the AppConfig object directly and re-run."
            )

    if dry_run:
        logger.success("Dry Run: Configuration Validated Successfully.")
        return

    simulation_dirs = setup_simulation_directories(
        output_dir=output_dir,
        checkpoint_dir=checkpoint_dir,
        data_dir=data_dir,
        tag=tag,
    )

    log_file_id = None
    try:
        log_file_id = logger.add(
            simulation_dirs.output_dir / "run.log", rotation="10 MB"
        )

        save_effective_config(
            run_dir=simulation_dirs.output_dir, config=resolved_config
        )
        save_simulation_metadata(simulation_dirs=simulation_dirs)

        logger.info("Starting Simulation...")
        build_and_run_simulation(
            config=resolved_config, simulation_dirs=simulation_dirs, resume=False
        )
        logger.success("Simulation terminated.")

    finally:
        if log_file_id is not None:  # pragma: no branch
            logger.remove(log_file_id)