| name | numpy-numerics |
| description | Numerical array operations for velocity fields, pump curves, and matrix calculations |
| category | packages |
| domain | general |
| complexity | basic |
| dependencies | ["numpy"] |
NumPy Numerics Skill
Overview
NumPy is the fundamental package for numerical computing in Python. It provides:
- Powerful N-dimensional array objects
- Broadcasting functions for element-wise operations
- Linear algebra, Fourier transform, and random number capabilities
- Tools for integrating C/C++ and Fortran code
- High-performance operations on large arrays
For engineering applications, NumPy is essential for:
- Processing velocity field data from CFD simulations
- Handling pump curve data and interpolation
- Solving linear systems (pipe networks, structural analysis)
- Statistical analysis of experimental data
- Numerical integration and differentiation
- Matrix operations for finite element methods
Installation
pip install numpy
For scientific computing with additional tools:
pip install numpy scipy matplotlib
Core Concepts
Arrays vs Lists
NumPy arrays are more efficient than Python lists for numerical operations:
import numpy as np
python_list = [1, 2, 3, 4, 5]
result = [x * 2 for x in python_list]
numpy_array = np.array([1, 2, 3, 4, 5])
result = numpy_array * 2
Key Differences:
- Arrays have fixed size and homogeneous type
- Arrays support vectorized operations
- Arrays use contiguous memory (cache-friendly)
- Arrays integrate with C/Fortran libraries
Array Creation
import numpy as np
a = np.array([1, 2, 3, 4, 5])
x = np.arange(0, 10, 0.5)
x = np.linspace(0, 10, 100)
data = np.zeros((10, 10))
grid = np.ones((5, 5))
I = np.eye(3)
x = np.linspace(0, 1, 50)
y = np.linspace(0, 1, 50)
X, Y = np.meshgrid(x, y)
Array Indexing and Slicing
import numpy as np
a = np.array([10, 20, 30, 40, 50])
print(a[0])
print(a[-1])
print(a[1:4])
data = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(data[0, 0])
print(data[:, 0])
print(data[0, :])
print(data[1:, 1:])
a = np.array([1, 2, 3, 4, 5])
mask = a > 2
print(a[mask])
Engineering Applications
1. Velocity Field Calculations
Computing velocity fields from flow data:
import numpy as np
x = np.linspace(0, 1, 50)
y = np.linspace(0, 1, 50)
X, Y = np.meshgrid(x, y)
r = np.sqrt((X - 0.5)**2 + (Y - 0.5)**2)
theta = np.arctan2(Y - 0.5, X - 0.5)
R = 0.1
u = 1.0 * (1 - R**2 / r**2) * np.cos(theta)
v = -1.0 * (1 - R**2 / r**2) * np.sin(theta)
velocity_magnitude = np.sqrt(u**2 + v**2)
max_velocity = np.max(velocity_magnitude)
max_location = np.unravel_index(
np.argmax(velocity_magnitude),
velocity_magnitude.shape
)
print(f"Maximum velocity: {max_velocity:.2f} m/s")
print(f"At location: ({X[max_location]:.2f}, {Y[max_location]:.2f})")
mean_velocity = np.mean(velocity_magnitude)
std_velocity = np.std(velocity_magnitude)
print(f"Mean velocity: {mean_velocity:.2f} ± {std_velocity:.2f} m/s")
2. Pump Curve Data Handling
Working with pump performance data:
import numpy as np
Q_data = np.array([0, 20, 40, 60, 80, 100, 120])
H_data = np.array([85, 84, 82, 78, 72, 64, 52])
eta_data = np.array([0, 45, 68, 78, 80, 75, 65])
coeffs = np.polyfit(Q_data, H_data, 2)
print(f"Pump curve: H = {coeffs[0]:.6f}*Q² + {coeffs[1]:.4f}*Q + {coeffs[2]:.2f}")
Q_smooth = np.linspace(0, 120, 100)
H_smooth = np.polyval(coeffs, Q_smooth)
bep_index = np.argmax(eta_data)
Q_bep = Q_data[bep_index]
H_bep = H_data[bep_index]
eta_bep = eta_data[bep_index]
print(f"\nBest Efficiency Point:")
print(f" Flow rate: {Q_bep:.1f} m³/h")
print(f" Head: {H_bep:.1f} m")
print(f" Efficiency: {eta_bep:.1f}%")
Q_target = 65
H_target = np.interp(Q_target, Q_data, H_data)
print(f"\nAt Q = {Q_target} m³/h: H = {H_target:.1f} m")
3. Matrix Operations for Linear Systems
Solving pipe network equations:
import numpy as np
A = np.array([
[1, 1, 0, 0],
[-1, 0, 1, 0],
[0, -1, -1, 1],
[0, 0, 0, -1]
])
b = np.array([10, 0, 0, -10])
Q = np.linalg.solve(A, b)
print("Flow rates in pipes:")
for i, q in enumerate(Q, 1):
print(f" Pipe {i}: {q:.2f} L/s")
residual = np.dot(A, Q) - b
print(f"\nResidual (should be ~0): {np.max(np.abs(residual)):.2e}")
cond = np.linalg.cond(A)
print(f"Condition number: {cond:.2f}")
if cond > 1000:
print("Warning: Matrix is ill-conditioned")
4. Interpolation of Experimental Data
Interpolating pressure drop data:
import numpy as np
Q_exp = np.array([0, 10, 20, 30, 40, 50, 60])
dP_exp = np.array([0, 5, 18, 38, 65, 98, 138])
Q_query = 25
dP_linear = np.interp(Q_query, Q_exp, dP_exp)
print(f"Linear interpolation at Q={Q_query}: dP={dP_linear:.1f} kPa")
coeffs = np.polyfit(Q_exp, dP_exp, 2)
dP_poly = np.polyval(coeffs, Q_query)
print(f"Polynomial fit at Q={Q_query}: dP={dP_poly:.1f} kPa")
from scipy import interpolate
f_spline = interpolate.interp1d(Q_exp, dP_exp, kind='cubic')
Q_dense = np.linspace(0, 60, 100)
dP_spline = f_spline(Q_dense)
5. Numerical Integration
Calculating flow rate from velocity profile:
import numpy as np
R = 0.05
u_max = 2.0
r = np.linspace(0, R, 100)
u = u_max * (1 - (r/R)**2)
integrand = u * 2 * np.pi * r
Q_trapz = np.trapz(integrand, r)
Q_analytical = np.pi * R**2 * u_max / 2
print(f"Flow rate (numerical): {Q_trapz:.6f} m³/s")
print(f"Flow rate (analytical): {Q_analytical:.6f} m³/s")
print(f"Error: {abs(Q_trapz - Q_analytical)/Q_analytical * 100:.2f}%")
from scipy import integrate
Q_simps = integrate.simpson(integrand, r)
print(f"Flow rate (Simpson): {Q_simps:.6f} m³/s")
6. Numerical Differentiation
Computing pressure gradient from pressure field:
import numpy as np
x = np.linspace(0, 10, 100)
P = 500000 - 2000 * x
dP_dx = np.gradient(P, x)
print(f"Pressure gradient: {dP_dx[0]:.1f} Pa/m")
print(f"Expected: -2000 Pa/m")
x = np.linspace(0, 1, 50)
y = np.linspace(0, 1, 50)
X, Y = np.meshgrid(x, y)
P_field = 100000 - 5000 * X - 3000 * Y
dP_dx, dP_dy = np.gradient(P_field, x, y)
print(f"\nPressure gradient field:")
print(f" ∂P/∂x at (0.5, 0.5): {dP_dx[25, 25]:.1f} Pa/m")
print(f" ∂P/∂y at (0.5, 0.5): {dP_dy[25, 25]:.1f} Pa/m")
7. Statistical Analysis of Test Data
Analyzing pump test measurements:
import numpy as np
test_runs = np.array([
[78.5, 79.2, 78.8, 79.0, 78.6],
[77.8, 78.5, 78.0, 78.3, 77.9],
[79.1, 78.9, 79.3, 78.7, 79.0]
])
mean_efficiency = np.mean(test_runs)
std_efficiency = np.std(test_runs, ddof=1)
sem = std_efficiency / np.sqrt(test_runs.size)
print(f"Mean efficiency: {mean_efficiency:.2f}%")
print(f"Standard deviation: {std_efficiency:.2f}%")
print(f"Standard error: {sem:.2f}%")
print(f"95% confidence interval: {mean_efficiency:.2f} ± {1.96*sem:.2f}%")
z_scores = np.abs((test_runs - mean_efficiency) / std_efficiency)
outliers = test_runs[z_scores > 3]
if len(outliers) > 0:
print(f"\nOutliers detected: {outliers}")
else:
print("\nNo outliers detected")
print(f"\nData range: {np.min(test_runs):.2f}% to {np.max(test_runs):.2f}%")
print(f"Median: {np.median(test_runs):.2f}%")
print(f"25th percentile: {np.percentile(test_runs, 25):.2f}%")
print(f"75th percentile: {np.percentile(test_runs, 75):.2f}%")
Performance Tips for Large Arrays
1. Use Vectorization Instead of Loops
import numpy as np
import time
n = 1000000
start = time.time()
result = []
for i in range(n):
result.append(i ** 2)
loop_time = time.time() - start
start = time.time()
result = np.arange(n) ** 2
vectorized_time = time.time() - start
print(f"Loop: {loop_time:.3f} s")
print(f"Vectorized: {vectorized_time:.3f} s")
print(f"Speedup: {loop_time/vectorized_time:.0f}x")
2. Preallocate Arrays
import numpy as np
result = np.array([])
for i in range(1000):
result = np.append(result, i)
result = np.zeros(1000)
for i in range(1000):
result[i] = i
result = np.arange(1000)
3. Use In-Place Operations
import numpy as np
a = np.random.rand(1000000)
b = a * 2
b = a + 5
a *= 2
a += 5
4. Choose Appropriate Data Types
import numpy as np
a = np.array([1.0, 2.0, 3.0])
print(f"Size: {a.nbytes} bytes")
a = np.array([1.0, 2.0, 3.0], dtype=np.float32)
print(f"Size: {a.nbytes} bytes (50% reduction)")
counts = np.array([1, 2, 3], dtype=np.uint8)
5. Use Memory Views for Large Arrays
import numpy as np
data = np.arange(1000000)
view = data[::2]
view[0] = 999
copy = data[::2].copy()
copy[0] = 888
6. Leverage Broadcasting
import numpy as np
a = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
b = np.array([10, 20, 30])
result = a + b
Common Pitfalls
1. Array vs Scalar Division
import numpy as np
a = np.array([1, 2, 3])
result = a / 2
result = a // 2
2. Copying vs Viewing
import numpy as np
a = np.array([1, 2, 3])
b = a
b[0] = 999
c = a.copy()
c[0] = 888
3. Integer Overflow
import numpy as np
a = np.array([100, 50], dtype=np.int8)
result = a + a
a = np.array([100, 50], dtype=np.int32)
result = a + a
Best Practices
- Vectorize operations - Avoid Python loops when possible
- Use appropriate dtypes - Balance precision and memory
- Preallocate arrays - Don't grow arrays in loops
- Leverage broadcasting - Implicit dimension matching
- Check array shapes - Use
.shape to debug dimension errors
- Use built-in functions - NumPy functions are optimized
- Profile code - Identify bottlenecks before optimizing
- Document units - Add comments for physical quantities
Integration with Engineering Workflows
NumPy integrates seamlessly with:
- Pandas: DataFrames built on NumPy arrays
- Matplotlib: Plotting arrays directly
- SciPy: Advanced scientific computing (interpolation, optimization)
- Fluids: Engineering calculations on array data
- CoolProp: Thermodynamic property tables
Example workflow:
import numpy as np
import matplotlib.pyplot as plt
from fluids import Reynolds, friction_factor
V = np.linspace(0.1, 5.0, 50)
D = 0.1
rho = 1000
mu = 0.001
Re = np.array([Reynolds(v, D, rho, mu) for v in V])
eD = 0.0001
f = np.array([friction_factor(re, eD) for re in Re])
plt.plot(Re, f, 'b-', linewidth=2)
plt.xlabel('Reynolds Number')
plt.ylabel('Friction Factor')
plt.grid(True)
plt.xscale('log')
plt.yscale('log')
plt.title('Friction Factor vs Reynolds Number')
plt.show()
References
Further Learning