Multivariate Calculus & Vector Operations
Multivariate calculus extends differentiation to functions of multiple variables, enabling analysis of vector fields, optimization landscapes, and tensor operations. This section covers gradients, Jacobians, Hessians, and their applications in scientific computing and machine learning.
🎯 Core Concepts
Scalar Functions: f: ℝⁿ → ℝ (multiple inputs, single output) - Gradient: ∇f = [∂f/∂x₁, ∂f/∂x₂, …, ∂f/∂xₙ] - direction of steepest ascent - Hessian: H_f = ∂²f/∂xᵢ∂xⱼ - matrix of second derivatives - Laplacian: ∇²f = tr(H_f) - sum of diagonal Hessian elements
Vector Functions: f: ℝⁿ → ℝᵐ (multiple inputs, multiple outputs) - Jacobian: J_f = ∂fᵢ/∂xⱼ - matrix of all first-order partial derivatives
Physical Interpretations: - Gradient: Electric field from potential, temperature gradient - Jacobian: Velocity field transformation, system linearization - Hessian: Curvature, optimization landscape - Laplacian: Heat diffusion, wave propagation
🔧 MultivariateDerivatives Class
The MultivariateDerivatives class provides a unified interface for multivariate calculus operations:
from pydelt.multivariate import MultivariateDerivatives
from pydelt.interpolation import SplineInterpolator
# Create multivariate derivatives object
mv = MultivariateDerivatives(
interpolator_class=SplineInterpolator,
smoothing=0.1 # Parameters passed to interpolator
)
# Fit to data
mv.fit(input_data, output_data)
# Compute various derivatives
gradient_func = mv.gradient() # For scalar functions
jacobian_func = mv.jacobian() # For vector functions
hessian_func = mv.hessian() # Second derivatives
laplacian_func = mv.laplacian() # Scalar from Hessian trace
🌋 Example 1: Optimization Landscape Analysis
Classic Example: Rosenbrock Function
The Rosenbrock function is a classic optimization test case with a curved valley:
import numpy as np
import matplotlib.pyplot as plt
from pydelt.multivariate import MultivariateDerivatives
from pydelt.interpolation import SplineInterpolator
# Rosenbrock function: f(x,y) = (a-x)² + b(y-x²)²
# Global minimum at (a,a²) with a=1, b=100
def rosenbrock(x, y, a=1, b=100):
return (a - x)**2 + b * (y - x**2)**2
def rosenbrock_gradient(x, y, a=1, b=100):
df_dx = -2*(a - x) - 4*b*x*(y - x**2)
df_dy = 2*b*(y - x**2)
return np.array([df_dx, df_dy])
# Generate training data on a grid
x = np.linspace(-2, 2, 25)
y = np.linspace(-1, 3, 25)
X, Y = np.meshgrid(x, y)
# Flatten for input
input_data = np.column_stack([X.flatten(), Y.flatten()])
output_data = rosenbrock(X.flatten(), Y.flatten())
# Fit multivariate derivatives
mv = MultivariateDerivatives(SplineInterpolator, smoothing=0.1)
mv.fit(input_data, output_data)
# Compute gradient function
gradient_func = mv.gradient()
# Evaluate gradient at test points
test_points = np.array([
[0.0, 0.0], # Away from minimum
[1.0, 1.0], # At minimum
[0.5, 0.25], # On the valley floor
[-1.0, 1.0] # Another test point
])
gradients_numerical = gradient_func(test_points)
gradients_analytical = np.array([rosenbrock_gradient(p[0], p[1]) for p in test_points])
print("Gradient Analysis:")
for i, point in enumerate(test_points):
num_grad = gradients_numerical[i]
ana_grad = gradients_analytical[i]
error = np.linalg.norm(num_grad - ana_grad)
print(f"Point {point}: Numerical {num_grad}, Analytical {ana_grad}, Error: {error:.4f}")
# Find critical points (where gradient ≈ 0)
# Create dense evaluation grid
x_dense = np.linspace(-2, 2, 100)
y_dense = np.linspace(-1, 3, 100)
X_dense, Y_dense = np.meshgrid(x_dense, y_dense)
eval_points = np.column_stack([X_dense.flatten(), Y_dense.flatten()])
gradients_dense = gradient_func(eval_points)
gradient_magnitudes = np.linalg.norm(gradients_dense, axis=1)
# Find minimum gradient magnitude (critical point)
min_idx = np.argmin(gradient_magnitudes)
critical_point = eval_points[min_idx]
min_gradient_mag = gradient_magnitudes[min_idx]
print(f"\nCritical point found at: ({critical_point[0]:.3f}, {critical_point[1]:.3f})")
print(f"Gradient magnitude: {min_gradient_mag:.6f}")
print(f"True minimum at: (1.000, 1.000)")
🌊 Example 2: Fluid Dynamics - Vector Field Analysis
Application: Analysis of 2D fluid flow with vorticity and divergence computation.
# Double gyre flow - classic fluid dynamics example
def double_gyre_velocity(x, y, t=0, A=0.1, omega=0.2, epsilon=0.25):
"""Double gyre velocity field - chaotic mixing flow"""
a = epsilon * np.sin(omega * t)
b = 1 - 2 * epsilon * np.sin(omega * t)
f = a * x**2 + b * x
u = -np.pi * A * np.sin(np.pi * f) * np.cos(np.pi * y)
v = np.pi * A * np.cos(np.pi * f) * np.sin(np.pi * y) * (2*a*x + b)
return u, v
# Generate velocity field data
x = np.linspace(0, 2, 30)
y = np.linspace(0, 1, 15)
X, Y = np.meshgrid(x, y)
U, V = double_gyre_velocity(X, Y)
# Prepare data for multivariate analysis
input_data = np.column_stack([X.flatten(), Y.flatten()])
output_data = np.column_stack([U.flatten(), V.flatten()]) # Vector field
# Fit multivariate derivatives for vector function
mv_vector = MultivariateDerivatives(SplineInterpolator, smoothing=0.05)
mv_vector.fit(input_data, output_data)
# Compute Jacobian matrix for flow analysis
jacobian_func = mv_vector.jacobian()
# Evaluate at analysis points
analysis_points = np.array([
[0.5, 0.5], # Center of left gyre
[1.5, 0.5], # Center of right gyre
[1.0, 0.5], # Saddle point
[0.2, 0.8], # Edge region
])
jacobians = jacobian_func(analysis_points)
print("Flow Field Analysis:")
for i, point in enumerate(analysis_points):
J = jacobians[i] # 2x2 Jacobian matrix
# Extract components: J = [[∂u/∂x, ∂u/∂y], [∂v/∂x, ∂v/∂y]]
du_dx, du_dy = J[0, :]
dv_dx, dv_dy = J[1, :]
# Compute flow properties
divergence = du_dx + dv_dy # ∇·v (expansion/contraction)
vorticity = dv_dx - du_dy # ∇×v (rotation)
# Strain rate tensor components
strain_rate = 0.5 * (J + J.T) # Symmetric part
rotation_rate = 0.5 * (J - J.T) # Antisymmetric part
# Eigenvalues for stability analysis
eigenvals = np.linalg.eigvals(J)
print(f"\nPoint ({point[0]:.1f}, {point[1]:.1f}):")
print(f" Divergence: {divergence:.4f}")
print(f" Vorticity: {vorticity:.4f}")
print(f" Eigenvalues: {eigenvals[0]:.4f} + {eigenvals[1]:.4f}i")
# Classify flow behavior
if np.abs(divergence) < 0.01 and np.abs(vorticity) > 0.1:
flow_type = "Rotational (vortex)"
elif np.abs(vorticity) < 0.01 and divergence > 0.1:
flow_type = "Divergent (source)"
elif np.abs(vorticity) < 0.01 and divergence < -0.1:
flow_type = "Convergent (sink)"
elif np.real(eigenvals).prod() < 0:
flow_type = "Saddle point"
else:
flow_type = "Complex flow"
print(f" Flow type: {flow_type}")
🔬 Example 3: Heat Diffusion Analysis
Application: Temperature field analysis with Laplacian computation for heat equation.
# 2D heat distribution with multiple sources
def temperature_field(x, y):
"""Temperature field with multiple heat sources"""
# Heat source at (0.3, 0.3)
T1 = 100 * np.exp(-((x - 0.3)**2 + (y - 0.3)**2) / 0.1**2)
# Heat source at (0.7, 0.7)
T2 = 80 * np.exp(-((x - 0.7)**2 + (y - 0.7)**2) / 0.15**2)
# Heat sink at (0.5, 0.8)
T3 = -60 * np.exp(-((x - 0.5)**2 + (y - 0.8)**2) / 0.08**2)
# Background temperature
T_bg = 20
return T1 + T2 + T3 + T_bg
def temperature_laplacian_analytical(x, y):
"""Analytical Laplacian for validation"""
# This would be complex to compute analytically
# Using finite differences for validation
h = 0.001
d2T_dx2 = (temperature_field(x+h, y) - 2*temperature_field(x, y) + temperature_field(x-h, y)) / h**2
d2T_dy2 = (temperature_field(x, y+h) - 2*temperature_field(x, y) + temperature_field(x, y-h)) / h**2
return d2T_dx2 + d2T_dy2
# Generate temperature measurement data
x = np.linspace(0, 1, 40)
y = np.linspace(0, 1, 40)
X, Y = np.meshgrid(x, y)
# Add measurement noise
T_true = temperature_field(X, Y)
T_measured = T_true + 0.5 * np.random.randn(*T_true.shape)
# Prepare data
input_data = np.column_stack([X.flatten(), Y.flatten()])
output_data = T_measured.flatten()
# Fit multivariate derivatives
mv_temp = MultivariateDerivatives(SplineInterpolator, smoothing=0.2)
mv_temp.fit(input_data, output_data)
# Compute gradient and Laplacian
gradient_func = mv_temp.gradient()
laplacian_func = mv_temp.laplacian()
# Analysis points
analysis_points = np.array([
[0.3, 0.3], # Near heat source 1
[0.7, 0.7], # Near heat source 2
[0.5, 0.8], # Near heat sink
[0.5, 0.5], # Center region
[0.1, 0.1], # Edge region
])
gradients = gradient_func(analysis_points)
laplacians = laplacian_func(analysis_points)
print("Heat Diffusion Analysis:")
for i, point in enumerate(analysis_points):
grad = gradients[i]
lapl = laplacians[i]
# Heat flux (proportional to negative gradient)
heat_flux_magnitude = np.linalg.norm(grad)
heat_flux_direction = -grad / (heat_flux_magnitude + 1e-10)
print(f"\nPoint ({point[0]:.1f}, {point[1]:.1f}):")
print(f" Temperature gradient: [{grad[0]:.2f}, {grad[1]:.2f}] K/m")
print(f" Heat flux magnitude: {heat_flux_magnitude:.2f}")
print(f" Heat flux direction: [{heat_flux_direction[0]:.3f}, {heat_flux_direction[1]:.3f}]")
print(f" Laplacian (∇²T): {lapl:.2f} K/m²")
# Interpret Laplacian for heat equation ∂T/∂t = α∇²T
if lapl > 1:
diffusion_behavior = "Net heat accumulation (heating up)"
elif lapl < -1:
diffusion_behavior = "Net heat loss (cooling down)"
else:
diffusion_behavior = "Near thermal equilibrium"
print(f" Diffusion behavior: {diffusion_behavior}")
⚙️ Advanced Features
Mixed Partial Derivatives
For functions requiring cross-derivatives:
# Compute mixed partials ∂²f/∂x∂y
hessian_func = mv.hessian()
hessians = hessian_func(test_points)
for i, H in enumerate(hessians):
mixed_partial = H[0, 1] # ∂²f/∂x∂y = ∂²f/∂y∂x
print(f"Mixed partial at point {i}: {mixed_partial:.4f}")
Tensor Operations
For higher-dimensional tensor calculus:
# Vector field curl in 3D (requires 3D vector field)
# ∇×F = [∂Fz/∂y - ∂Fy/∂z, ∂Fx/∂z - ∂Fz/∂x, ∂Fy/∂x - ∂Fx/∂y]
# For 2D vector fields, scalar curl:
jacobian = jacobian_func(point)
curl_2d = jacobian[1, 0] - jacobian[0, 1] # ∂v/∂x - ∂u/∂y
Interpolator Method Comparison
Different interpolators for different applications:
from pydelt.interpolation import LowessInterpolator, NeuralNetworkInterpolator
# Robust to noise
mv_robust = MultivariateDerivatives(LowessInterpolator, frac=0.3)
# High accuracy with automatic differentiation
mv_neural = MultivariateDerivatives(NeuralNetworkInterpolator,
hidden_layers=[128, 64], epochs=2000)
🎓 Best Practices
Data Preparation: 1. Grid vs Scattered Data: Regular grids work best, but scattered data is supported 2. Data Density: Ensure sufficient sampling in regions of interest 3. Noise Handling: Use robust interpolators (LOWESS) for noisy data 4. Scaling: Normalize input coordinates to similar ranges
Method Selection: - Smooth Functions: SplineInterpolator for best accuracy - Noisy Data: LowessInterpolator for robustness - High Precision: NeuralNetworkInterpolator with automatic differentiation - Large Datasets: Consider computational cost vs accuracy trade-offs
Validation: Always validate against analytical solutions when available:
# Compare numerical vs analytical gradients
grad_numerical = gradient_func(test_points)
grad_analytical = analytical_gradient(test_points)
errors = np.linalg.norm(grad_numerical - grad_analytical, axis=1)
print(f"RMS gradient error: {np.sqrt(np.mean(errors**2)):.6f}")
Physical Interpretation: - Gradient: Points in direction of steepest increase - Divergence > 0: Source behavior (expansion) - Divergence < 0: Sink behavior (contraction) - Vorticity ≠ 0: Rotational flow - Laplacian > 0: Concave up (local minimum tendency) - Laplacian < 0: Concave down (local maximum tendency)
⚠️ Limitations
Mixed Partials: Traditional interpolation methods approximate mixed partials as zero. For exact mixed derivatives, use neural networks with automatic differentiation.
Boundary Effects: Derivatives near data boundaries may be less accurate due to extrapolation.
Curse of Dimensionality: Accuracy decreases with increasing input dimensions. Consider dimensionality reduction for high-dimensional problems.
🔗 Next Steps
Multivariate calculus provides the foundation for advanced stochastic computing. The next level introduces:
Stochastic Computing: Probabilistic derivatives with uncertainty quantification
Stochastic Link Functions: Model derivatives under different probability distributions
Financial Applications: Option pricing, risk analysis, and volatility modeling
The gradient and Jacobian computations become especially powerful when combined with stochastic transformations for uncertainty propagation and risk analysis.