03 · Arrays are the working language¶
Scientific Python becomes useful when you stop treating an array as “many numbers” and start treating its shape as part of the model.
Decision: does each axis retain a declared physical meaning?
Output: a vectorised, shape-preserving numerical transformation.
Frame¶
A time series may have shape (500,). A field sampled on a grid might have
shape (256, 128). A parameter sweep can have shape (n_oh, n_bo, n_time).
Those axes are not interchangeable.
Check shapes before values when an operation surprises you.
Predict¶
For
predict:
- each shape;
- the first and last value;
- the exact derivative;
- which samples have neighbours on both sides.
Then run it.
Indexing answers a question¶
The Boolean array late has the same shape as time. It represents a
predicate for every sample. Applying it to a different-length array is a model
error, not merely an indexing inconvenience.
Slices preserve order:
These three arrays have the same shape. That alignment is the basis of a centred difference:
Broadcasting needs an axis story¶
oh * bo fails because shapes (3,) and (4,) do not align. If you want every
pair:
oh_grid = oh[:, None] # shape (3, 1)
bo_grid = bo[None, :] # shape (1, 4)
combined = oh_grid * bo_grid # shape (3, 4)
The added axes express the intended outer product. Do not add None until you
can say what each axis means.
Vectorisation is not a moral virtue¶
Vectorisation is excellent when:
- one operation applies independently to an array region;
- shapes express the algorithm;
- NumPy performs the work more clearly and efficiently.
A loop is better when:
- each case may fail differently;
- processing has side effects;
- the next step depends on a convergence history;
- clarity would be lost in a dense expression.
In CoMPhy post-processing, vectorise arithmetic within one case and loop or parallelise across independent cases. That boundary often matches the scientific structure.
Numerical arrays have a data type¶
Integer division produces floats, but storing into an integer array can still
truncate. Strings loaded from a CSV do not become physical numbers until you
convert and validate them. nan and inf are floats, so type alone does not
guarantee validity.
A visible finite-difference contract¶
The reference implementation deliberately accepts only a uniform grid:
"""Numerical reductions with explicit assumptions."""
from typing import Any
import numpy as np
from numpy.typing import ArrayLike, NDArray
from .io import SimulationLog
def _one_dimensional(
name: str,
values: ArrayLike,
) -> NDArray[np.float64]:
array = np.asarray(values, dtype=float)
if array.ndim != 1:
raise ValueError(f"{name} must be one-dimensional")
if not np.all(np.isfinite(array)):
raise ValueError(
f"{name} must contain only finite values",
)
return array
def central_difference(
x: ArrayLike,
y: ArrayLike,
) -> NDArray[np.float64]:
"""Differentiate sampled data on a uniformly spaced grid.
Interior points use a centred difference. The endpoints use a
second-order one-sided difference. A non-uniform grid is
refused so the numerical assumption remains explicit.
"""
coordinate = _one_dimensional("x", x)
values = _one_dimensional("y", y)
if coordinate.size != values.size:
raise ValueError("x and y must have the same length")
if coordinate.size < 3:
raise ValueError("at least three samples are required")
spacing = np.diff(coordinate)
if np.any(spacing <= 0):
raise ValueError("x must be strictly increasing")
if not np.allclose(
spacing,
spacing[0],
rtol=1e-10,
atol=1e-14,
):
raise ValueError(
"central_difference requires a uniform grid",
)
Refusing non-uniform spacing is better than applying a uniform formula silently. A more general algorithm can be added when the course needs it.
Verify¶
Complete Exercise 03. Use \(y=x^2\) because the second-order boundary and interior formulas should reproduce its derivative to roundoff on a uniform grid.
Then change one coordinate to make the grid non-uniform. The correct behaviour for this exercise is a clear error.
Reflect¶
For every array in your solution, annotate:
If that feels excessive for a five-line exercise, imagine the same ambiguity inside a three-dimensional parameter sweep.