Source code for decent_bench.schemes
import random
from abc import ABC, abstractmethod
from functools import cached_property
import numpy as np
from numpy import float64
from numpy.random import MT19937, Generator
from numpy.typing import NDArray
[docs]
class AgentActivationScheme(ABC):
"""Scheme defining how agents go active/inactive over the course of the algorithm execution."""
[docs]
@abstractmethod
def is_active(self, iteration: int) -> bool:
"""
Whether or not the agent is active.
Args:
iteration: current iteration of algorithm execution
"""
[docs]
class AlwaysActive(AgentActivationScheme):
"""Scheme that makes the agent always active."""
[docs]
def is_active(self, iteration: int) -> bool: # noqa: D102, ARG002
return True
[docs]
class CompressionScheme(ABC):
"""Scheme defining how messages are compressed when sent over the network."""
[docs]
@abstractmethod
def compress(self, msg: NDArray[float64]) -> NDArray[float64]:
"""Apply compression and return a new, compressed message."""
[docs]
class NoCompression(CompressionScheme):
"""Scheme that leaves messages uncompressed."""
[docs]
def compress(self, msg: NDArray[float64]) -> NDArray[float64]: # noqa: D102
return msg
[docs]
class Quantization(CompressionScheme):
"""Scheme that rounds each element in a message to *significant_digits*."""
def __init__(self, n_significant_digits: int):
self.n_significant_digits = n_significant_digits
[docs]
def compress(self, msg: NDArray[float64]) -> NDArray[float64]: # noqa: D102
res: NDArray[float64] = np.vectorize(lambda x: float(f"%.{self.n_significant_digits - 1}e" % x))(msg)
return res
[docs]
class DropScheme(ABC):
"""Scheme defining how message drops occur over the network."""
[docs]
@abstractmethod
def should_drop(self) -> bool:
"""Whether or not to drop."""
[docs]
class NoDrops(DropScheme):
"""Scheme that never drops messages."""
[docs]
def should_drop(self) -> bool: # noqa: D102
return False
[docs]
class NoiseScheme(ABC):
"""Scheme defining how noise impacts messages."""
[docs]
@abstractmethod
def make_noise(self, msg: NDArray[float64]) -> NDArray[float64]:
"""Apply noise scheme without mutating the *msg* passed in."""
[docs]
class NoNoise(NoiseScheme):
"""Scheme that leaves messages untouched."""
[docs]
def make_noise(self, msg: NDArray[float64]) -> NDArray[float64]: # noqa: D102
return msg
[docs]
class GaussianNoise(NoiseScheme):
"""Scheme that applies Gaussian noise - that is, noise following a normal distribution."""
def __init__(self, mean: float, sd: float):
if sd < 0:
raise ValueError("Standard deviation (sd) must be non-negative for Gaussian noise.")
self.mean = mean
self.sd = sd
@cached_property
def _generator(self) -> Generator:
return Generator(MT19937())
[docs]
def make_noise(self, msg: NDArray[float64]) -> NDArray[float64]: # noqa: D102
return msg + self._generator.normal(self.mean, self.sd, msg.shape)