5.4.17 · D4Scientific Computing (Python)

Exercises — 3D plots — surface, wireframe

2,640 words12 min readBack to topic

Before we begin, one shared picture to keep in your head — the checkerboard floor + poles:

Figure — 3D plots — surface, wireframe

Every little square on the floor has a position . At each square we stand a pole of height . meshgrid gives us the positions of all the squares; the function gives us the pole heights; plot_surface drapes a sheet over the pole tops.


Level 1 — Recognition

These test whether you can read the notation and name the pieces.

Recall Solution 1.1

The rule from the parent note: meshgrid(x, y) returns two arrays of shape .

  • WHAT: len(y) = 2 (that's 10, 20) and len(x) = 4 (that's 1,2,3,4).
  • WHY this order: rows vary in , columns vary in — like an image indexed (row, col).
  • So and . Both are the same shape.

Concretely:

X = [[1,2,3,4],      Y = [[10,10,10,10],
     [1,2,3,4]]           [20,20,20,20]]

repeats down each row; repeats across each column.

Recall Solution 1.2
  • ax.plot_surface → filled colored patches (supports a cmap that maps height to color).
  • ax.plot_wireframe → a see-through mesh of lines (usually one solid color=). Think DRAPE: a bedsheet (surface) vs a fishing net (wireframe).
Recall Solution 1.3

The keyword is projection='3d'.

ax = fig.add_subplot(projection='3d')

WHY: the 3D drawing methods (plot_surface, plot_wireframe) only exist on a 3D axes object. Ask a plain 2D axes for plot_surface and you get an AttributeError.


Level 2 — Application

Now you compute actual numbers and grids.

Recall Solution 2.1
  • WHAT: first write the coordinate grids.
X = [[0,1,2],      Y = [[ 0, 0, 0],
     [0,1,2]]           [10,10,10]]
  • WHY elementwise: Z = X + Y adds cell-by-cell (this is NumPy vectorization) — no loop needed.
  • Add matching cells: Z.shape is (2, 3) — same as X and Y.
Recall Solution 2.2
  • WHAT: the plot is literally the function drawn, so read the height by plugging the coordinates into .
  • .
  • WHY it looks like a bowl: is the squared distance from the origin, so it grows in every direction from the minimum at .
Recall Solution 2.3
  • Let be the distance from the origin. Here .
  • WHY : it's the Pythagorean distance — this surface only cares how far you are from the center, not the direction, so it makes concentric ripples.
  • . That's the top of the first ripple crest.

Level 3 — Analysis

Reason about strides, shapes, and why a plot looks the way it does.

Recall Solution 3.1
  • WHAT rstride means: row stride = draw every -th grid row line; cstride does the same for columns.
  • (a) len(y) = 61, so there are 61 row lines before striding.
  • (b) Drawing every 10th of 61: indices lines.
  • WHY thin the mesh: a full mesh is a dense hairball; striding trades detail for a readable see-through cage.
Recall Solution 3.2
  • X.shape = Y.shape = (len(y), len(x)) = (40, 100).
  • Z = np.cos(X) * Y is elementwise, so Z.shape = (40, 100) too.
  • All three grids matchplot_surface(X, Y, Z) succeeds. The requirement is simply X.shape == Y.shape == Z.shape.
Recall Solution 3.3
  • x is 1D with shape (len(x),), so np.sin(x) is also 1D of shape (len(x),).
  • But X and Y are 2D of shape (len(y), len(x)).
  • WHY it fails: plot_surface needs a full 2D table of heights, one per floor cell. A 1D Z has no height for most cells → a shape mismatch error.
  • Fix: always feed the grid arrays into : Z = np.sin(X).

Level 4 — Synthesis

Assemble complete, correct scripts from scratch.

Recall Solution 4.1

Follow GRID → HEIGHT → DRAPE:

import numpy as np
import matplotlib.pyplot as plt
 
x = np.linspace(-2, 2, 80)          # GRID: even, endpoint-inclusive sampling
y = np.linspace(-2, 2, 80)
X, Y = np.meshgrid(x, y)            # 2D coordinate grids, shape (80, 80)
Z = X**2 - Y**2                    # HEIGHT: vectorized, same shape as X
 
fig = plt.figure()
ax = fig.add_subplot(projection='3d')   # 3D axes — required
ax.plot_surface(X, Y, Z, cmap='viridis')  # DRAPE the colored sheet
ax.set_xlabel('x'); ax.set_ylabel('y'); ax.set_zlabel('z')
plt.show()

WHY linspace not arange: we want exactly 80 evenly spaced points including both ends — smoother surface. See Colormaps in Matplotlib for cmap choices. What it looks like: a Pringle chip — rising along the -axis, falling along the -axis.

Figure — 3D plots — surface, wireframe
Recall Solution 4.2

Replace the surface call:

ax.plot_wireframe(X, Y, Z, rstride=8, cstride=8, color='green')
  • WHY rstride=8, cstride=8: an mesh is far too dense; every 8th line gives lines per direction — a clean cage.
  • WHY no cmap: wireframes are lines, not patches — a colormap has no per-face patch to color, so we pass a single color=.
Recall Solution 4.3
  • WHAT meshgrid does: repeat x down the rows, repeat y across the columns.
import numpy as np
x = np.array([1,2,3]); y = np.array([10,20])
X = np.tile(x, (len(y), 1))          # x as a row, stacked len(y) times
Y = np.tile(y.reshape(-1,1), (1, len(x)))  # y as a column, stretched len(x) wide

Result:

X = [[1,2,3],      Y = [[10,10,10],
     [1,2,3]]           [20,20,20]]
  • WHY it matches: this is exactly what meshgrid and broadcasting does internally — X from broadcasting a row, Y from broadcasting a column.

Level 5 — Mastery

Predict the picture, then confirm — "Forecast-then-Verify".

Recall Solution 5.1
  • Let . The surface is — concentric ripples because it depends only on distance .
  • First trough where : the smallest positive is .
  • At : — back to floor level, one full ripple completed.
  • WHY concentric: since only enters, every point on a circle of radius has the same height → rings, like a pond after a stone drops.
Figure — 3D plots — surface, wireframe
Recall Solution 5.2
  • (a) The exponent is largest (least negative) at the origin where it equals , so . Peak height at .
  • (b) .
  • (c) As , , so . The bump flattens to the floor — a single smooth hill that never quite reaches .
  • WHY and not : using keeps it smooth and rotationally symmetric with a rounded top; a bare would give a sharp cone tip at the origin.
Recall Solution 5.3
  • Use a contour plot (ax.contourf(X, Y, Z)), viewed on a plain 2D axes.
  • What stays the same: the entire X, Y, Z grid — a contour is literally the surface's height map seen from the top, colored by height, exactly as colormaps color the surface.
  • WHY it works: both a surface and a contour answer "what is at each floor point?"; one drapes a sheet, the other paints the floor.
Recall Solution 5.4
  • A grid of rows by columns of points creates gaps down and gaps across.
  • Each pair of gaps bounds one quadrilateral facet → total facets .
  • For : facets.
  • WHY this matters: facet count explodes with grid size (this is why rstride/cstride exist) — a grid is facets, too dense to see.

Recall

Recall

meshgrid(x,y) with len(x)=5, len(y)=3 gives X.shape = ? ::: (3, 5), i.e. (len(y), len(x)). Height of Z = X2 + Y2 at (3,4)? ::: 25. Height of Z = sin(sqrt(X²+Y²)) at (0, π/2)? ::: sin(π/2) = 1. First-trough radius of z = sin(r)? ::: r = 3π/2 ≈ 4.712. Peak of z = e^{-(x²+y²)} and where? ::: 1, at the origin. Facets drawn by plot_surface for m×n points? ::: (n−1)(m−1). Does bigger rstride give more or less detail? ::: Less — it skips more lines.


Connections

  • Parent: 3D plots — surface, wireframe
  • meshgrid and broadcasting
  • NumPy vectorization
  • Matplotlib figure and axes objects
  • Contour plots
  • Colormaps in Matplotlib
  • 2D plots — line, scatter

same grid

L1 Recognition names the pieces

L2 Application computes Z

L3 Analysis shapes and strides

L4 Synthesis full scripts

L5 Mastery predict then verify

Contour view from above