Visualizing Summations from Concrete Mathematics
Mathematical formulas are often much easier to understand when you can see them. A table of numbers tells you the values; a graph tells you the shape — whether something is growing slowly, quickly, linearly, or curving upward like a parabola. This post takes the summation formulas from Concrete Mathematics and brings them to life in Python, step by step.
The Summation We Are Visualizing
We will focus on the sum of the first \(n\) positive integers:
$$\sum_{k=1}^{n} k = 1 + 2 + 3 + \cdots + n = \frac{n(n+1)}{2}$$The right-hand side is the closed form — a direct formula that gives the answer without looping. For \(n = 10\): \(\frac{10 \times 11}{2} = 55\). Let's verify that with a table before writing any plotting code:
| \(n\) | Running total | Closed form \(\frac{n(n+1)}{2}\) | Match |
|---|---|---|---|
| 1 | 1 | 1 | ✓ |
| 2 | 3 | 3 | ✓ |
| 3 | 6 | 6 | ✓ |
| 4 | 10 | 10 | ✓ |
| 5 | 15 | 15 | ✓ |
| 6 | 21 | 21 | ✓ |
| 7 | 28 | 28 | ✓ |
| 8 | 36 | 36 | ✓ |
| 9 | 45 | 45 | ✓ |
| 10 | 55 | 55 | ✓ |
Computing Partial Sums with accumulate
A partial sum is the running total after adding each new term. The sequence of partial sums for \(\sum_{k=1}^{n} k\) is \(1, 3, 6, 10, 15, \ldots\) — each entry is the previous entry plus the next integer.
Python's itertools.accumulate does exactly this — it takes a sequence and produces
the running totals one step at a time:
from itertools import accumulate
def partial_sums(n: int) -> list[int]:
return list(accumulate(range(1, n + 1)))
Let's unpack every piece:
range(1, n + 1) — produces the integers \(1, 2, 3, \ldots, n\).
The n + 1 is needed because Python's range(a, b) excludes \(b\) itself.
accumulate(...) — takes the sequence and produces running totals.
Given \([1, 2, 3, 4, 5]\), it produces \([1, 3, 6, 10, 15]\):
| Input value added | Running total after this step |
|---|---|
| 1 | 1 |
| 2 | \(1 + 2 = 3\) |
| 3 | \(3 + 3 = 6\) |
| 4 | \(6 + 4 = 10\) |
| 5 | \(10 + 5 = 15\) |
list(...) — accumulate is a lazy iterator that
produces values one at a time only when asked. Wrapping it in list() forces
everything to be computed immediately and stored in a list we can reuse.
print(partial_sums(10))
# [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
A Quick Introduction to Matplotlib
Matplotlib is the most widely used Python library for creating graphs and charts. Before looking at the plotting code, it helps to understand its two core objects, because they appear in every matplotlib program:
| Object | What it is | Analogy |
|---|---|---|
fig (Figure) |
The entire image — the outer container for everything | A blank sheet of paper |
ax (Axes) |
The actual chart area — axes, grid, plotted lines, labels | The graph drawn on that paper |
The line fig, ax = plt.subplots(figsize=(8, 5)) creates both at once.
figsize=(8, 5) sets the image size to 8 inches wide by 5 inches tall —
you can change those numbers to make it larger or smaller. Once you have ax,
all drawing commands go through it:
| Command | What it does |
|---|---|
ax.plot(x, y) | Draws a line connecting the (x, y) data points |
ax.set_title(...) | Adds a title above the chart |
ax.set_xlabel(...) | Labels the horizontal axis |
ax.set_ylabel(...) | Labels the vertical axis |
ax.legend() | Shows the key explaining what each line is |
ax.grid(True) | Adds a background grid for easier reading |
plt.tight_layout() | Prevents labels from being cut off at the edges |
plt.show() | Renders and displays the finished graph |
Plotting the Partial Sums
import matplotlib.pyplot as plt
from itertools import accumulate
n = 50
x = list(range(1, n + 1)) # [1, 2, ..., 50]
y = list(accumulate(range(1, n + 1))) # partial sums
closed = [k * (k + 1) // 2 for k in x] # closed form n(n+1)/2 at each n
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(x, y, label="Partial sums", linewidth=2)
ax.plot(x, closed, label="Closed form", linewidth=1.5, linestyle="--")
ax.set_title("Growth of the Summation sum k from 1 to n")
ax.set_xlabel("n")
ax.set_ylabel("Sum")
ax.legend()
ax.grid(True)
plt.tight_layout()
plt.show()
Here is what the code produces. The solid line (partial sums computed step by step) and the dashed line (closed form \(\frac{n(n+1)}{2}\)) lie exactly on top of each other — confirming both approaches give identical results:
Why the Curve Is Quadratic
The curve bends upward like a parabola rather than rising in a straight line. This is because the closed form \(\frac{n(n+1)}{2} \approx \frac{n^2}{2}\) for large \(n\) — it grows proportionally to \(n^2\), which is the definition of quadratic growth.
We can confirm this numerically by checking the ratio of the sum to \(n^2\). If the sum truly grows like \(\frac{n^2}{2}\), then \(\frac{\text{sum}}{n^2}\) should approach \(0.5\) as \(n\) gets large:
| \(n\) | \(\frac{n(n+1)}{2}\) | \(n^2\) | Ratio \(\frac{\text{sum}}{n^2}\) |
|---|---|---|---|
| 5 | 15 | 25 | 0.600 |
| 10 | 55 | 100 | 0.550 |
| 20 | 210 | 400 | 0.525 |
| 50 | 1275 | 2500 | 0.510 |
| 100 | 5050 | 10000 | 0.505 |
The ratio converges toward \(0.5\) as \(n\) grows, confirming that the sum grows at exactly half the rate of \(n^2\) — in other words, \(\Theta(n^2)\) in Big-O notation.
Comparing Multiple Summations
The real payoff of visualization is comparing different summations side by side to see how their growth rates differ. Let's plot three at once:
| Summation | Closed form | Growth rate |
|---|---|---|
| \(\sum_{k=1}^{n} 1\) | \(n\) | Linear — \(\Theta(n)\) |
| \(\sum_{k=1}^{n} k\) | \(\frac{n(n+1)}{2}\) | Quadratic — \(\Theta(n^2)\) |
| \(\sum_{k=1}^{n} k^2\) | \(\frac{n(n+1)(2n+1)}{6}\) | Cubic — \(\Theta(n^3)\) |
from itertools import accumulate
import matplotlib.pyplot as plt
n = 50
x = list(range(1, n + 1))
sum_ones = list(accumulate(1 for k in x)) # adds 1 each step — grows linearly
sum_k = list(accumulate(k for k in x)) # adds k each step — grows quadratically
sum_k_sq = list(accumulate(k**2 for k in x)) # adds k squared each step — grows cubically
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(x, sum_ones, label="sum 1 — linear", linewidth=2)
ax.plot(x, sum_k, label="sum k — quadratic", linewidth=2)
ax.plot(x, sum_k_sq, label="sum k squared — cubic", linewidth=2)
ax.set_title("Comparing Growth Rates of Three Summations")
ax.set_xlabel("n")
ax.set_ylabel("Cumulative sum")
ax.legend()
ax.grid(True)
plt.tight_layout()
plt.show()
The three curves pull apart dramatically as \(n\) grows. The linear sum rises gently, the quadratic bends upward, and the cubic climbs steeply. Seeing this visually makes the difference between \(O(n)\), \(O(n^2)\), and \(O(n^3)\) algorithms immediately tangible — what looks like a small difference in the formula has a large practical impact on performance.
If you enjoy practical Python exploration, you may also like: Python Requests vs urllib.
Comments