User Guide#

This user guide shows you different examples of how to use decent-bench.

Installation#

Requires Python 3.13+

pip install decent-bench

Sunny case#

Benchmark algorithms on a regression problem without any communication constraints, using only default settings.

from decent_bench import benchmark, benchmark_problem
from decent_bench.cost_functions import LinearRegressionCost
from decent_bench.distributed_algorithms import ADMM, DGD, GT2

if __name__ == "__main__":
    benchmark.benchmark(
        algorithms=[
            DGD(iterations=1000, step_size=0.001),
            GT2(iterations=1000, step_size=0.001),
            ADMM(iterations=1000, rho=10, alpha=0.3),
        ],
        benchmark_problem=benchmark_problem.create_regression_problem(LinearRegressionCost),
    )

Execution settings#

Configure settings for metrics, trials, statistical confidence level, logging, and multiprocessing.

from logging import DEBUG

from decent_bench import benchmark, benchmark_problem
from decent_bench.cost_functions import LinearRegressionCost
from decent_bench.distributed_algorithms import ADMM, DGD
from decent_bench.metrics.plot_metrics import GlobalCostErrorPerIteration
from decent_bench.metrics.table_metrics import NrGradientCalls

if __name__ == "__main__":
    benchmark.benchmark(
        algorithms=[DGD(iterations=1000, step_size=0.001), ADMM(iterations=1000, rho=10, alpha=0.3)],
        benchmark_problem=benchmark_problem.create_regression_problem(LinearRegressionCost),
        table_metrics=[NrGradientCalls([min, max])],
        plot_metrics=[GlobalCostErrorPerIteration()],
        table_fmt="latex",
        n_trials=10,
        confidence_level=0.9,
        log_level=DEBUG,
        max_processes=1,
    )

Benchmark problems#

Configure out-of-the-box regression problems#

Configure communication constraints and other settings for out-of-the-box regression problems.

from decent_bench import benchmark, benchmark_problem
from decent_bench.cost_functions import LinearRegressionCost
from decent_bench.distributed_algorithms import ADMM, DGD, GT2

problem = benchmark_problem.create_regression_problem(
    LinearRegressionCost,
    n_agents=100,
    n_neighbors_per_agent=3,
    asynchrony=True,
    compression=True,
    noise=True,
    drops=True,
)

if __name__ == "__main__":
    benchmark.benchmark(
        algorithms=[
            DGD(iterations=1000, step_size=0.001),
            GT2(iterations=1000, step_size=0.001),
            ADMM(iterations=1000, rho=10, alpha=0.3),
        ],
        benchmark_problem=problem,
    )

Modify existing problems#

Change the settings of an already created benchmark problem, for example, the network topology.

import networkx as nx

from decent_bench import benchmark, benchmark_problem
from decent_bench.cost_functions import LinearRegressionCost
from decent_bench.distributed_algorithms import ADMM, DGD, GT2

n_agents = 100
n_neighbors_per_agent = 3

problem = benchmark_problem.create_regression_problem(
    LinearRegressionCost,
    n_agents=n_agents,
    n_neighbors_per_agent=n_neighbors_per_agent,
    asynchrony=True,
    compression=True,
    noise=True,
    drops=True,
)

problem.topology_structure = nx.random_regular_graph(n_agents, n_neighbors_per_agent)

if __name__ == "__main__":
    benchmark.benchmark(
        algorithms=[
            DGD(iterations=1000, step_size=0.001),
            GT2(iterations=1000, step_size=0.001),
            ADMM(iterations=1000, rho=10, alpha=0.3),
        ],
        benchmark_problem=problem,
    )

Create problems using existing resources#

Create a custom benchmark problem using existing resources.

import networkx as nx

from decent_bench import benchmark
from decent_bench import centralized_algorithms as ca
from decent_bench.benchmark_problem import BenchmarkProblem
from decent_bench.cost_functions import LogisticRegressionCost
from decent_bench.datasets import SyntheticClassificationData
from decent_bench.distributed_algorithms import ADMM, DGD, GT2
from decent_bench.schemes import GaussianNoise, Quantization, UniformActivationRate, UniformDropRate

n_agents = 100

dataset = SyntheticClassificationData(
    n_classes=2, n_partitions=n_agents, n_samples_per_partition=10, n_features=3
)

costs = [LogisticRegressionCost(*p) for p in dataset.get_training_partitions()]

sum_cost = sum(costs[1:], start=costs[0])
optimal_x = ca.accelerated_gradient_descent(
    sum_cost, x0=None, max_iter=50000, stop_tol=1e-100, max_tol=1e-16
)

problem = BenchmarkProblem(
    topology_structure=nx.random_regular_graph(3, n_agents, seed=0),
    cost_functions=costs,
    optimal_x=optimal_x,
    agent_activation_schemes=[UniformActivationRate(0.5)] * n_agents,
    compression_scheme=Quantization(n_significant_digits=4),
    noise_scheme=GaussianNoise(mean=0, sd=0.001),
    drop_scheme=UniformDropRate(drop_rate=0.5),
)

if __name__ == "__main__":
    benchmark.benchmark(
        algorithms=[
            DGD(iterations=1000, step_size=0.001),
            GT2(iterations=1000, step_size=0.001),
            ADMM(iterations=1000, rho=10, alpha=0.3),
        ],
        benchmark_problem=problem,
    )

Create problems from scratch#

Create a custom benchmark problem with your own dataset, cost function, and communication schemes by implementing the corresponding abstracts.

import networkx as nx

from decent_bench import benchmark
from decent_bench import centralized_algorithms as ca
from decent_bench.benchmark_problem import BenchmarkProblem
from decent_bench.cost_functions import CostFunction
from decent_bench.datasets import Dataset
from decent_bench.distributed_algorithms import DGD, GT1
from decent_bench.schemes import AgentActivationScheme, CompressionScheme, DropScheme, NoiseScheme

class MyDataset(Dataset): ...

class MyCostFunction(CostFunction): ...

class MyAgentActivationScheme(AgentActivationScheme): ...

class MyCompressionScheme(CompressionScheme): ...

class MyNoiseScheme(NoiseScheme): ...

class MyDropScheme(DropScheme): ...

n_agents = 100

costs = [MyCostFunction(*p) for p in MyDataset().get_training_partitions()]

sum_cost = sum(costs[1:], start=costs[0])
optimal_x = ca.accelerated_gradient_descent(
    sum_cost, x0=None, max_iter=50000, stop_tol=1e-100, max_tol=1e-16
)

problem = BenchmarkProblem(
    topology_structure=nx.random_regular_graph(3, n_agents, seed=0),
    cost_functions=costs,
    optimal_x=optimal_x,
    agent_activation_schemes=[MyAgentActivationScheme()] * n_agents,
    compression_scheme=MyCompressionScheme(),
    noise_scheme=MyNoiseScheme(),
    drop_scheme=MyDropScheme(),
)

if __name__ == "__main__":
    benchmark.benchmark(
        algorithms=[DGD(iterations=1000, step_size=0.001), GT1(iterations=1000, step_size=0.001)],
        benchmark_problem=problem,
    )

Algorithms#

Create a new algorithm to benchmark against existing ones.

Note: In order for metrics to work, use Agent.x to update the local primal variable. Similarly, in order for the benchmark problem’s communication schemes to be applied, use the Network object to retrieve agents and to send and receive messages.

import numpy as np

from decent_bench import benchmark, benchmark_problem
from decent_bench.cost_functions import LinearRegressionCost
from decent_bench.distributed_algorithms import ADMM, DGD, DstAlgorithm
from decent_bench.network import Network

class MyNewAlgorithm(DstAlgorithm):
    name: str = "MNA"

    def __init__(self, iterations: int, step_size: float):
        self.iterations = iterations
        self.step_size = step_size

    def run(self, network: Network) -> None:
        # Initialize agents
        for agent in network.get_all_agents():
            x0 = np.zeros(agent.cost_function.domain_shape)
            y0 = np.zeros(agent.cost_function.domain_shape)
            neighbors = network.get_neighbors(agent)
            agent.initialize(x=x0, received_msgs=dict.fromkeys(neighbors, x0), aux_vars={"y": y0})

        # Run iterations
        W = network.metropolis_weights
        for k in range(self.iterations):
            for i in network.get_active_agents(k):
                i.aux_vars["y_new"] = i.x - self.step_size * i.cost_function.gradient(i.x)
                neighborhood_avg = np.sum(
                    [W[i, j] * x_j for j, x_j in i.received_messages.items()], axis=0
                )
                neighborhood_avg += W[i, i] * i.x
                i.x = i.aux_vars["y_new"] - i.aux_vars["y"] + neighborhood_avg
                i.aux_vars["y"] = i.aux_vars["y_new"]
            for i in network.get_active_agents(k):
                network.broadcast(i, i.x)
            for i in network.get_active_agents(k):
                network.receive_all(i)

if __name__ == "__main__":
    benchmark.benchmark(
        algorithms=[
            MyNewAlgorithm(iterations=1000, step_size=0.001),
            DGD(iterations=1000, step_size=0.001),
            ADMM(iterations=1000, rho=10, alpha=0.3),
        ],
        benchmark_problem=benchmark_problem.create_regression_problem(LinearRegressionCost),
    )

Metrics#

Create your own metrics to tabulate and/or plot.

import numpy.linalg as la

from decent_bench import benchmark, benchmark_problem
from decent_bench.agent import AgentMetricsView
from decent_bench.benchmark_problem import BenchmarkProblem
from decent_bench.cost_functions import LinearRegressionCost
from decent_bench.distributed_algorithms import ADMM, DGD
from decent_bench.metrics.plot_metrics import DEFAULT_PLOT_METRICS, PlotMetric, X, Y
from decent_bench.metrics.table_metrics import DEFAULT_TABLE_METRICS, TableMetric

def x_error_at_iter(agent: AgentMetricsView, problem: BenchmarkProblem, i: int = -1) -> float:
    return float(la.norm(problem.optimal_x - agent.x_per_iteration[i]))

class XError(TableMetric):
    description: str = "x error"

    def get_data_from_trial(
        self, agents: list[AgentMetricsView], problem: BenchmarkProblem
    ) -> list[float]:
        return [x_error_at_iter(a, problem) for a in agents]

class MaxXErrorPerIteration(PlotMetric):
    x_label: str = "iteration"
    y_label: str = "max x error"

    def get_data_from_trial(
        self, agents: list[AgentMetricsView], problem: BenchmarkProblem
    ) -> list[tuple[X, Y]]:
        iter_reached_by_all = min(len(a.x_per_iteration) for a in agents)
        res: list[tuple[X, Y]] = []
        for i in range(iter_reached_by_all):
            y = max([x_error_at_iter(a, problem, i) for a in agents])
            res.append((i, y))
        return res

if __name__ == "__main__":
    benchmark.benchmark(
        algorithms=[
            DGD(iterations=1000, step_size=0.001),
            ADMM(iterations=1000, rho=10, alpha=0.3),
        ],
        benchmark_problem=benchmark_problem.create_regression_problem(LinearRegressionCost),
        table_metrics=DEFAULT_TABLE_METRICS + [XError([min, max])],
        plot_metrics=DEFAULT_PLOT_METRICS + [MaxXErrorPerIteration()],
    )

Output#

Benchmark executions will have outputs like these:

_images/table.png _images/plot.png