water_filling <- function(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 maximize the total
## channel capacity. The variable x_i represents the transmitter power
## allocated to the ith channel, and log(alpha_i + x_i) gives the
## capacity or maximum communication rate of the channel. The objective
## is to maximize sum(log(alpha_i + x_i)) subject to sum(x_i) = sum_x.
## Declare variables and parameters
x <- Variable(n)
alpha <- a
## Objective: maximize the total communication rate
obj <- Maximize(sum(log(alpha + x)))
## Constraints
constraints <- list(x >= 0, sum(x) == sum_x)
## Solve
prob <- Problem(obj, constraints)
result <- psolve(prob)
prob_status <- status(prob)
if (prob_status == "optimal") {
list(status = prob_status,
value = result,
x = as.vector(value(x)))
} else {
list(status = prob_status,
value = NA,
x = NA)
}
}Water Filling in Communications
Introduction
From Boyd and Vandenberghe, Convex Optimization, Example 5.2 (page 145).
Convex optimization can be used to solve the classic water filling problem. This problem is where a total amount of power
where
This form is straightforward to express in DCP format and thus can be simply solved using CVXR.
Water Filling Function
We define a function that solves the water-filling problem for given channel parameters and total power budget.
Example
As a simple example, we set
The function outputs the problem status, the maximum communication rate, and the power allocation required to achieve this maximum communication rate.
buckets <- 3
alpha <- c(0.8, 1.0, 1.2)
sol <- water_filling(buckets, alpha)
cat("Problem status:", sol$status, "\n")
cat(sprintf("Optimal communication rate = %.4g\n", sol$value))
cat("Transmitter powers:\n")
print(round(sol$x, 3))Problem status: optimal
Optimal communication rate = 0.863
Transmitter powers:
[1] 0.533 0.333 0.133
Visualization
To illustrate the water filling principle, we plot
x_vals <- sol$x
y_vals <- alpha + x_vals
df <- data.frame(
bucket = seq_len(buckets),
alpha = alpha,
total = y_vals,
power = x_vals
)
ggplot(df) +
geom_col(aes(x = bucket, y = alpha), fill = "steelblue", width = 0.8) +
geom_col(aes(x = bucket, y = total), fill = "lightblue", alpha = 0.5, width = 0.8) +
geom_hline(yintercept = max(y_vals), linetype = "dashed", color = "red") +
scale_x_continuous(breaks = seq_len(buckets)) +
labs(x = "Bucket Number", y = "Power Level",
title = "Water Filling Solution") +
ylim(0, 1.5) +
theme_minimal()
Larger Example
We now solve a larger water filling problem with
n_large <- 6
alpha_large <- c(0.2, 0.5, 0.8, 1.0, 1.2, 1.5)
P_large <- 2
sol_large <- water_filling(n_large, alpha_large, sum_x = P_large)
cat("Problem status:", sol_large$status, "\n")
cat(sprintf("Optimal communication rate = %.4g\n", sol_large$value))
cat("Transmitter powers:\n")
print(round(sol_large$x, 3))Problem status: optimal
Optimal communication rate = 1.059
Transmitter powers:
[1] 0.925 0.625 0.325 0.125 0.000 0.000
x_vals_l <- sol_large$x
y_vals_l <- alpha_large + x_vals_l
df_l <- data.frame(
bucket = seq_len(n_large),
alpha = alpha_large,
total = y_vals_l,
power = x_vals_l
)
ggplot(df_l) +
geom_col(aes(x = bucket, y = alpha), fill = "steelblue", width = 0.8) +
geom_col(aes(x = bucket, y = total), fill = "lightblue", alpha = 0.5, width = 0.8) +
geom_hline(yintercept = max(y_vals_l), linetype = "dashed", color = "red") +
scale_x_continuous(breaks = seq_len(n_large)) +
labs(x = "Bucket Number", y = "Power Level",
title = "Water Filling Solution (6 Channels)") +
theme_minimal()
Session Info
R version 4.5.2 (2025-10-31)
Platform: aarch64-apple-darwin20
Running under: macOS Tahoe 26.3
Matrix products: default
BLAS: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: America/Los_Angeles
tzcode source: internal
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] ggplot2_4.0.2 CVXR_1.8.1
loaded via a namespace (and not attached):
[1] gmp_0.7-5.1 generics_0.1.4 clarabel_0.11.2 slam_0.1-55
[5] lattice_0.22-9 digest_0.6.39 magrittr_2.0.4 evaluate_1.0.5
[9] grid_4.5.2 RColorBrewer_1.1-3 fastmap_1.2.0 jsonlite_2.0.0
[13] Matrix_1.7-4 ECOSolveR_0.6.1 backports_1.5.0 scs_3.2.7
[17] Rmosek_11.1.1 scales_1.4.0 codetools_0.2-20 cli_3.6.5
[21] rlang_1.1.7 Rglpk_0.6-5.1 withr_3.0.2 yaml_2.3.12
[25] otel_0.2.0 tools_4.5.2 osqp_1.0.0 Rcplex_0.3-8
[29] checkmate_2.3.4 dplyr_1.2.0 gurobi_13.0-1 vctrs_0.7.1
[33] R6_2.6.1 lifecycle_1.0.5 htmlwidgets_1.6.4 pkgconfig_2.0.3
[37] cccp_0.3-3 pillar_1.11.1 gtable_0.3.6 glue_1.8.0
[41] Rcpp_1.1.1 xfun_0.56 tibble_3.3.1 tidyselect_1.2.1
[45] knitr_1.51 dichromat_2.0-0.1 highs_1.12.0-3 farver_2.1.2
[49] htmltools_0.5.9 rmarkdown_2.30 labeling_0.4.3 piqp_0.6.2
[53] compiler_4.5.2 S7_0.2.1