codex/darcy-flux-and-pore-velocity.py

darcy-flux-and-pore-velocity.py
"""Illustrate the homogeneous Darcy slab; save a PNG in the current directory.

Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
Run from _media: python ../darcy-flux-and-pore-velocity.py
"""
from pathlib import Path

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle, Circle
import numpy as np

k, mu, rho, g = 1e-12, 1e-3, 1000.0, 9.81
length, area, phi, drop = 0.5, 0.02, 0.25, 10000.0
q = k * drop / (mu * length)
flow = area * q
v = q / phi
transit = length / v
plt.rcParams.update({'font.size': 11, 'figure.facecolor': 'white', 'axes.facecolor': 'white'})
blue, orange, gray = '#166a9c', '#bb5429', '#647580'
fig = plt.figure(figsize=(9, 5.5), dpi=120)
grid = fig.add_gridspec(2, 2, height_ratios=[1.1, 1], hspace=0.6, wspace=0.36)
slab = fig.add_subplot(grid[0, :])
pressure = fig.add_subplot(grid[1, 0])
speeds = fig.add_subplot(grid[1, 1])
fig.subplots_adjust(left=0.09, right=0.96, top=0.81, bottom=0.18)
fig.suptitle('One discharge, two velocity averages', fontsize=18, y=0.97)
fig.text(0.5, 0.895, r'$k=10^{-12}\ \mathrm{m^2},\quad \mu=10^{-3}\ \mathrm{Pa\,s},\quad \phi=0.25$', ha='center', fontsize=12)
slab.set(xlim=(-0.17, 0.67), ylim=(-0.08, 0.3))
slab.axis('off')
slab.add_patch(Rectangle((0, 0), length, 0.22, fc='#edf5fa', ec=blue, lw=1.8))
for x in np.linspace(0.035, 0.465, 9):
    for y in [0.04, 0.11, 0.18]:
        slab.add_patch(Circle((x, y), 0.021, fc='#bcc6cc', ec='white', lw=0.7))
slab.annotate('', xy=(0.65, 0.11), xytext=(0.51, 0.11), arrowprops={'arrowstyle': '-|>', 'color': blue, 'lw': 2.5, 'mutation_scale': 18})
slab.text(-0.025, 0.11, 'Inlet\n10 kPa', ha='right', va='center', color=blue)
slab.text(0.59, 0.25, 'Outlet: 0 kPa', ha='center', color=blue)
slab.annotate('', xy=(0.5, -0.035), xytext=(0, -0.035), arrowprops={'arrowstyle': '<->', 'color': gray})
slab.text(0.25, -0.042, 'L = 0.50 m; bulk face area A = 0.020 m²', ha='center', va='top', fontsize=10)
slab.text(0.61, 0.018, f'Q = {flow*1e6:.2f} mL/s', ha='center', color=blue, fontsize=11)
slab.text(0.25, 0.265, 'Horizontal saturated slab (pores drawn schematically)', ha='center', fontsize=11)
x = np.linspace(0, length, 101)
pressure.plot(x, drop*(1-x/length)/1000, lw=2.5, color=blue)
pressure.set(xlim=(0, length), ylim=(0, 10.7), xlabel='Distance x (m)', ylabel='Gauge pressure (kPa)', title='Constant pressure gradient')
pressure.grid(alpha=0.2)
speeds.barh([1, 0], np.array([q, v])*1e6, color=[blue, orange], height=0.5)
speeds.set(yticks=[1, 0], yticklabels=['Darcy q', 'Pore v'], xlim=(0, 103), xlabel='Mean axial velocity (µm/s)', title='The pore mean is four times larger')
for y, value, label in [(1, q, 'Q/A = 20'), (0, v, 'q/φ = 80')]:
    speeds.text(value*1e6+2, y, label, va='center', fontsize=10)
speeds.spines[['top', 'right']].set_visible(False)
fig.text(0.5, 0.075, f'Pore volume = {phi*area*length*1000:.1f} L     |     Ideal transit time = L/v = {transit:.0f} s = {transit/3600:.2f} h', ha='center', fontsize=12)
fig.text(0.5, 0.025, 'Bulk area includes solid grains; pore velocity averages only over the fluid-filled pore volume.', ha='center', fontsize=10)
fig.savefig(Path.cwd() / (Path(__file__).stem + '.png'), facecolor='white', transparent=False)
plt.close(fig)