"""Generate flat-plate-velocity-traverse-apparatus.png in the working directory. Tested with Python 3.14, matplotlib 3.10.7, numpy 2.3.5. This is an illustrative pressure-averaging model, not measured laboratory data. The assumed 0.50 mm sensing footprint is not a reported probe bore diameter. """ from pathlib import Path import importlib.util import json import sys import matplotlib.pyplot as plt from matplotlib.patches import Rectangle import numpy as np sys.dont_write_bytecode = True spec = importlib.util.spec_from_file_location( "laminar_profile", Path(__file__).with_name("flat-plate-laminar-velocity-profiles.py")) profile = importlib.util.module_from_spec(spec) spec.loader.exec_module(profile) def sample_model(eta, states, centres, footprint, U, nu, x, rho): y = eta * np.sqrt(nu * x / U) offsets = np.linspace(-footprint / 2, footprint / 2, 301) locations = centres[:, None] + offsets[None, :] assert np.min(locations) >= 0 speeds = U * np.interp(locations, y, states[:, 1]) q_samples = np.trapezoid(0.5 * rho * speeds**2, offsets, axis=1) / footprint mean_speed = np.trapezoid(speeds, offsets, axis=1) / footprint inferred = np.sqrt(2 * q_samples / rho) return q_samples, inferred, mean_speed def main(): eta, states = profile.blasius_profile() U, nu, x, rho, footprint = 10.0, 1.5e-5, 0.3048, 1.2, 0.00050 centres = np.array([0.25, 0.45, 0.7, 1.0, 1.35, 1.7, 2.1, 2.6, 3.3, 4.2]) * 1e-3 q, inferred, mean = sample_model(eta, states, centres, footprint, U, nu, x, rho) assert np.all(inferred >= mean - 1e-12) plt.rcParams.update({"font.size": 11, "figure.facecolor": "white"}) fig, (sketch, readout) = plt.subplots(1, 2, figsize=(10.6, 5.3), gridspec_kw={"width_ratios": [1.55, 1]}, layout="constrained") fig.suptitle("Sampling a flat-plate velocity profile with pressure", fontsize=15) sketch.set(xlim=(0, 10), ylim=(-1.1, 7.5)) sketch.axis("off") sketch.plot([0.1, 9.7], [0.3, 0.3], color="gray", lw=1) sketch.plot([0.1, 9.7], [6.8, 6.8], color="gray", lw=1) sketch.add_patch(Rectangle((1, 1.1), 6.0, 0.13, color="#424b55")) sketch.text(1.1, 0.7, "Stationary smooth plate", fontsize=11) positions = np.linspace(1, 7, 150) edge = 1.23 + 0.9 * np.sqrt((positions - 1) / 6) sketch.fill_between(positions, 1.23, edge, color="#e0edf4") sketch.plot(positions, edge, ls="--", color="#1f6f9b") sketch.text(1.2, 2.35, "Thin velocity deficit\n(normal scale exaggerated)", fontsize=10) for yy in [3.6, 4.6]: sketch.annotate("", (2, yy), (0.25, yy), arrowprops={"arrowstyle": "->", "lw": 2, "color": "#1f6f9b"}) sketch.text(0.25, 5.1, "Incoming U", fontsize=11, color="#1f6f9b") # Upstream-facing two-port probe, carried by a normal traverse. sketch.plot([5.0, 6.5, 6.5], [1.78, 1.78, 5.4], lw=3, color="#313d48") sketch.scatter([5.0], [1.78], s=30, color="#b84145", zorder=4) sketch.scatter([5.55], [1.78], s=22, color="#437741", zorder=4) sketch.annotate("", (6.15, 4.6), (6.15, 2.5), arrowprops={"arrowstyle": "<->", "lw": 1.5}) sketch.text(4.5, 6.15, "Micrometer traverse\nrecords inlet-centre y", fontsize=11) sketch.annotate("Upstream opening: p₀(y)", (5, 1.78), (3.8, 3.45), fontsize=10, arrowprops={"arrowstyle": "->", "color": "#b84145"}) sketch.text(6.75, 2.6, "Static port: p(y)", fontsize=10, color="#437741") sketch.plot([6.5, 7.45, 7.45], [5.3, 5.3, 4.1], color="#b84145", lw=1.5) sketch.plot([6.6, 8.75, 8.75], [4.8, 4.8, 4.1], color="#437741", lw=1.5) sketch.add_patch(Rectangle((7.15, 3.2), 2.2, 0.85, facecolor="white", edgecolor="#424b55")) sketch.text(8.25, 3.63, "Manometer\nq = p₀ − p", ha="center", va="center", fontsize=10) sketch.text(7.45, 5.5, "Pressure lines", fontsize=10) sketch.text(0.4, -0.25, "Model x = 0.3048 m • U = 10 m/s\nρ = 1.2 kg/m³ • ν = 1.5 × 10⁻⁵ m²/s", fontsize=10, va="top") yy = eta * np.sqrt(nu * x / U) * 1000 readout.plot(states[:, 1], yy, lw=2, color="#1f6f9b", label="Ideal point profile") readout.scatter(inferred / U, centres * 1000, s=45, edgecolor="#b84145", facecolor="white", linewidth=1.5, label="Pressure-averaged model", zorder=4) readout.axhline(0, color="#424b55", lw=2) readout.set(xlim=(0, 1.1), ylim=(0, 4.8), xlabel=r"Inferred speed $\sqrt{2q/\rho}/U$", ylabel="Inlet-centre y (mm)") readout.spines[["top", "right"]].set_visible(False) readout.grid(alpha=0.17) readout.legend(loc="upper left", fontsize=9) readout.text(0.06, 3.5, "Synthetic readings only\nAssumed footprint: 0.50 mm\nProbe bore is unspecified", fontsize=9, bbox={"facecolor": "white", "alpha": 0.9, "edgecolor": "none"}) readout.text(0.45, 0.3, "Wall is unsampled", fontsize=10) fig.savefig(Path(__file__).stem + ".png", dpi=130, facecolor="white", transparent=False) plt.close(fig) print(json.dumps({"sample_count": len(centres), "min_centre_mm": float(centres.min() * 1000), "footprint_mm": footprint * 1000, "pressure_range_pa": [float(q.min()), float(q.max())], "max_rms_minus_mean_ms": float((inferred - mean).max())}, sort_keys=True)) if __name__ == "__main__": main()