Practical Session: ADMM
Decentralized Energy Market Clearing
This session explores how to solve a decentralized optimization problem using the Alternating Direction Method of Multipliers (ADMM). We will simulate a smart grid where households trade energy while keeping the grid balanced, without a central authority knowing everyone's preferences.
To implement a decentralized market clearing algorithm using ADMM, separating the problem into local agent updates and a global clearing step.
1. Problem Introduction
Consider a smart grid with households (agents). Each household has a preferred energy target :
- : Wants to buy energy.
- : Wants to sell energy (e.g., has solar panels).
The Constraint: The grid must be balanced. The sum of all trades must be zero ().
The Goal: Minimize the total "discomfort" (deviation from the target) for all agents.
The Global Problem
To solve this in a decentralized way (where no single entity knows all ), we reformulate it using ADMM by introducing an auxiliary variable .
ADMM Formulation (The Sharing Problem)
- : Auxiliary variables representing the "consensus" state.
- : Indicator function for the set . It is 0 if valid, if invalid.
2. Mathematical Derivation (Exercises)
1. The Augmented Lagrangian
We add the dual variable and the quadratic penalty :
2. The x-update (The Agent's Problem)
Each agent updates to minimize with respect to , treating and as constants.
Taking the derivative and setting to 0:
Interpretation: The agent balances their preference () against the market constraint () and the price signal ().
3. The z-update (The Clearinghouse Problem)
The central entity updates to minimize subject to .
This is equivalent to projecting the vector onto the zero-sum hyperplane. The projection of a vector onto the zero-mean set is simply removing its mean:
where .
4. The Dual Update
The standard ADMM dual update:
3. Python Implementation
We simulate households and verify that the market clears (sum of trades approaches 0).
import numpy as np
import matplotlib.pyplot as plt
# 1. Setup Simulation
np.random.seed(42)
N = 50
rho = 1.0
max_iter = 100
# Random targets between -10 and 10
d = np.random.uniform(-10, 10, N)
# Initialize variables
x = np.zeros(N)
z = np.zeros(N)
y = np.zeros(N)
residuals = []
market_imbalance = []
# 2. ADMM Loop
for k in range(max_iter): # --- Step 1: x-update (Agents) --- # This happens in parallel for each agent
x = (d + rho \* z - y) / (1 + rho)
# --- Step 2: z-update (Clearinghouse) ---
# Create the vector v to project
v = x + (1/rho) * y
# Project onto zero-sum set (subtract mean)
z = v - np.mean(v)
# --- Step 3: Dual update (Prices) ---
y = y + rho * (x - z)
# --- Monitoring ---
# Primal residual: how far are x and z apart?
r_prim = np.linalg.norm(x - z)
residuals.append(r_prim)
# Market imbalance: does sum(x) = 0?
imbalance = np.abs(np.sum(x))
market_imbalance.append(imbalance)
# 3. Visualization
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(residuals)
plt.yscale('log')
plt.title('Primal Residual Convergence')
plt.xlabel('Iteration')
plt.ylabel('||x - z||')
plt.grid(True)
plt.subplot(1, 2, 2)
plt.plot(market_imbalance, color='orange')
plt.yscale('log')
plt.title('Market Imbalance (Sum of Trades)')
plt.xlabel('Iteration')
plt.ylabel('|Sum(x)|')
plt.grid(True)
plt.tight_layout()
plt.show()
print(f"Final Imbalance: {np.sum(x):.4e}")
print(f"First 5 Agent Targets: {d[:5]}")
print(f"First 5 Agent Trades: {x[:5]}")
You should see the Market Imbalance drop exponentially. This confirms that even though agents only optimized for themselves locally, the ADMM coordination forced the global grid to balance perfectly.