Averaging Consensus

The Averaging Consensus algorithm distributes a parameter vector \(\lambda\) across \(N\) agents via a gossip-style protocol. Each agent maintains its own copy of \(\lambda\) and iteratively averages it with values received from neighbours. An optional gradient term allows each agent to steer the consensus towards locally desirable values.

Algorithm

Let \(\lambda_i^k\) be the value held by agent \(i\) at iteration \(k\). The update rule is:

\[\lambda_i^{k+1} = \lambda_i^k + \alpha \bigl(\bar{\lambda}^k - \lambda_i^k\bigr) + \nabla_i^k\]

where

  • \(\bar{\lambda}^k\) is the average of all received values at iteration \(k\)

  • \(\alpha \in (0,1]\) is the step size (mixing parameter)

  • \(\nabla_i^k = \text{gradient\_term}(\text{actor}_i, \lambda_i^k, \text{data})\) is the local gradient correction (zero by default)

The algorithm runs for a fixed number of iterations (max_iter) after which each agent calls a user-supplied finish_callback.

Usage

>>> from distributed_resource_optimization import (
...     create_averaging_consensus_participant,
...     create_averaging_consensus_start,
...     start_distributed_optimization,
... )
>>> finished = []
>>> def on_finish(algo, carrier):
...     finished.append(algo._lam.copy())
>>> actors = [
...     create_averaging_consensus_participant(on_finish, initial_lam=v, max_iter=200)
...     for v in [1.0, 5.0, 10.0]
... ]
>>> start = create_averaging_consensus_start(1.0, data=None)
>>> asyncio.run(start_distributed_optimization(actors, start))
>>> len(finished) > 0
True
>>> np.allclose(actors[0]._lam, actors[1]._lam, atol=1e-2)
True

Parameters

Parameter

Default

Description

finish_callback

Called with (algorithm, carrier) when max_iter is reached

consensus_actor

None

ConsensusActor providing gradient term

initial_lam

10.0

Starting scalar value broadcast to all \(\lambda\) components

alpha

0.3

Mixing step size

max_iter

50

Number of gossip rounds before finishing

Local Gradient Corrections

To steer the consensus, subclass ConsensusActor and override gradient_term():

>>> from distributed_resource_optimization import ConsensusActor
>>> class PushToTarget(ConsensusActor):
...     def __init__(self, target, step=0.05):
...         self.target = np.asarray(target)
...         self.step = step
...     def gradient_term(self, lam, data):
...         return self.step * (self.target - lam)

The data argument carries whatever was embedded in the initial AveragingConsensusMessage — useful for passing problem data alongside the consensus.

Economic Dispatch

The built-in LinearCostEconomicDispatchConsensusActor implements consensus-based economic dispatch. Each agent has a linear cost and power limits; the gradient term pushes \(\lambda\) toward the clearing price at which supply equals demand:

\[ \begin{align}\begin{aligned}P(\lambda) = \operatorname{clip}\!\left(\frac{\lambda - c}{\epsilon},\; P_{\min},\; P_{\max}\right)\\\nabla = -\rho \Bigl(P(\lambda) - \frac{P_{\text{target}}}{N}\Bigr)\end{aligned}\end{align} \]
>>> from distributed_resource_optimization import (
...     LinearCostEconomicDispatchConsensusActor,
...     create_averaging_consensus_participant,
...     AveragingConsensusMessage,
...     start_distributed_optimization,
... )
>>> actors = [
...     create_averaging_consensus_participant(
...         lambda *_: None,
...         LinearCostEconomicDispatchConsensusActor(cost=10, p_max=100, n_guess=3),
...         max_iter=100,
...     ),
...     create_averaging_consensus_participant(
...         lambda *_: None,
...         LinearCostEconomicDispatchConsensusActor(cost=10, p_max=100, n_guess=3),
...         max_iter=100,
...     ),
...     create_averaging_consensus_participant(
...         lambda *_: None,
...         LinearCostEconomicDispatchConsensusActor(cost=10, p_max=100, n_guess=3),
...         max_iter=100,
...     ),
... ]
>>> p_target = [10, 30, 40, 45, 60, 10]
>>> msg = AveragingConsensusMessage(lam=np.ones(len(p_target)) * 10, k=0, data=p_target)
>>> asyncio.run(start_distributed_optimization(actors, msg))
>>> np.allclose(actors[0]._lam, actors[1]._lam, atol=1e-3)
True

Note

The algorithm terminates after exactly max_iter gossip rounds — there is no residual-based stopping criterion. In a fully connected graph convergence is typically fast (10–30 rounds); increase max_iter for larger or sparser networks.

See Also

  • create_averaging_consensus_participant(), AveragingConsensusAlgorithm

  • ConsensusActor, NoConsensusActor

  • LinearCostEconomicDispatchConsensusActor