Water Filling in Communications =============================== by Robert Gowers, Roger Hill, Sami Al-Izzi, Timothy Pollington and Keith Briggs from Boyd and Vandenberghe, Convex Optimization, example 5.2 page 145 Convex optimisation can be used to solve the classic water filling problem. This problem is where a total amount of power :math:P has to be assigned to :math:n communication channels, with the objective of maximising the total communication rate. The communication rate of the :math:i\ th channel is given by: :math:\log(\alpha_i + x_i) where :math:x_i represents the power allocated to channel :math:i and :math:\alpha_i represents the floor above the baseline at which power can be added to the channel. Since :math:-\log(X) is convex, we can write the water-filling problem as a convex optimisation problem: minimise :math:\sum_{i=1}^N -\log(\alpha_i + x_i) subject to :math:x_i \succeq 0 and :math:\sum_{i=1}^N x_i = P This form is also very straightforward to put into DCP format and thus can be simply solved using CVXPY. .. code:: python #!/usr/bin/env python3 # @author: R. Gowers, S. Al-Izzi, T. Pollington, R. Hill & K. Briggs import numpy as np import cvxpy as cp .. code:: python def water_filling(n, a, sum_x=1): ''' Boyd and Vandenberghe, Convex Optimization, example 5.2 page 145 Water-filling. This problem arises in information theory, in allocating power to a set of n communication channels in order to maximise the total channel capacity. The variable x_i represents the transmitter power allocated to the ith channel, and log(α_i+x_i) gives the capacity or maximum communication rate of the channel. The objective is to minimise -∑log(α_i+x_i) subject to the constraint ∑x_i = 1 ''' # Declare variables and parameters x = cp.Variable(shape=n) alpha = cp.Parameter(n, nonneg=True) alpha.value = a # Choose objective function. Interpret as maximising the total communication rate of all the channels obj = cp.Maximize(cp.sum(cp.log(alpha + x))) # Declare constraints constraints = [x >= 0, cp.sum(x) - sum_x == 0] # Solve prob = cp.Problem(obj, constraints) prob.solve() if(prob.status=='optimal'): return prob.status, prob.value, x.value else: return prob.status, np.nan, np.nan Example ------- As a simple example, we set :math:N = 3, :math:P = 1 and :math:\boldsymbol{\alpha} = (0.8,1.0,1.2). The function outputs whether the problem status, the maximum communication rate and the power allocation required is achieved with this maximum communication rate. .. code:: python # As an example, we will solve the water filling problem with 3 buckets, each with different α np.set_printoptions(precision=3) buckets = 3 alpha = np.array([0.8, 1.0, 1.2]) .. code:: python stat, prob, x = water_filling(buckets, alpha) print('Problem status: {}'.format(stat)) print('Optimal communication rate = {:.4g} '.format(prob)) print('Transmitter powers:\n{}'.format(x)) .. parsed-literal:: Problem status: optimal Optimal communication rate = 0.863 Transmitter powers: [0.533 0.333 0.133] To illustrate the water filling principle, we will plot :math:\alpha_i + x_i and check that this level is flat where power has been allocated: .. code:: python import matplotlib import matplotlib.pylab as plt %matplotlib inline matplotlib.rcParams.update({'font.size': 14}) axis = np.arange(0.5,buckets+1.5,1) index = axis+0.5 X = x.copy() Y = alpha + X # to include the last data point as a step, we need to repeat it A = np.concatenate((alpha,[alpha[-1]])) X = np.concatenate((X,[X[-1]])) Y = np.concatenate((Y,[Y[-1]])) plt.xticks(index) plt.xlim(0.5,buckets+0.5) plt.ylim(0,1.5) plt.step(axis,A,where='post',label =r'$\alpha$',lw=2) plt.step(axis,Y,where='post',label=r'$\alpha + x$',lw=2) plt.legend(loc='lower right') plt.xlabel('Bucket Number') plt.ylabel('Power Level') plt.title('Water Filling Solution') plt.show() .. image:: water_filling_BVex5.2_files/water_filling_BVex5.2_7_0.png