# CFL Coupling and Limits

> Comprehensive guide for combining multiple stability constraints in time-dependent simulations.

- Skill: `tools-only/cfl-coupling-and-limits` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add tools-only/cfl-coupling-and-limits`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tools-only/cfl-coupling-and-limits/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tools-only (https://skillmd.com/u/tools-only)
- Updated: 2026-09-29
- Page: https://skillmd.com/skills/tools-only/cfl-coupling-and-limits

---

# CFL Coupling and Limits

Comprehensive guide for combining multiple stability constraints in time-dependent simulations.

## Fundamental Stability Limits

### Advection (CFL) Limit

For advection equation: ∂u/∂t + v·∇u = 0

```
dt_adv ≤ C_adv × dx / |v_max|

C_adv depends on scheme:
- First-order upwind: C_adv = 1.0
- Second-order central: C_adv = 1.0 (with proper time stepping)
- Third-order upwind: C_adv ≈ 0.87
- Fourth-order central: C_adv ≈ 0.72
- WENO5: C_adv ≈ 0.5-1.0 (depends on smoothness)
```

### Diffusion (Fourier) Limit

For diffusion equation: ∂u/∂t = D∇²u

```
dt_diff ≤ C_diff × dx² / D

In 1D: C_diff = 0.5 (explicit Euler)
In 2D: C_diff = 0.25 (explicit Euler)
In 3D: C_diff = 0.167 (explicit Euler)

Higher-order time stepping:
- RK4: C_diff up to ~2.8/dim
- DuFort-Frankel: unconditionally stable
- Implicit: no limit (but accuracy limits apply)
```

### Reaction Limit

For stiff reactions: du/dt = R(u)

```
dt_react ≤ C_react / |λ_max|

Where λ_max is the largest eigenvalue of the reaction Jacobian.

For simple cases:
- First-order kinetics: λ = k (rate constant)
- Combustion: λ can be 10⁶ - 10¹² (very stiff!)
```

### Acoustic/Pressure Wave Limit

For compressible flow:

```
dt_acoustic ≤ dx / c

Where c = sound speed = sqrt(γP/ρ) or sqrt(K/ρ)
```

## Combining Multiple Limits

### Minimum Rule

The most conservative approach:

```
dt_limit = min(dt_adv, dt_diff, dt_react, dt_acoustic, ...)
dt = safety × dt_limit
```

**Safety factor:**
| Situation | Safety |
|-----------|--------|
| Conservative | 0.5 |
| Normal | 0.8 |
| Aggressive | 0.95 |
| Testing | 0.99 |

### Harmonic Mean (Less Conservative)

For coupled physics where limits interact:

```
1/dt_limit = 1/dt_adv + 1/dt_diff

Equivalent to: dt_limit = (dt_adv × dt_diff) / (dt_adv + dt_diff)
```

### Dimensional Coupling

In 2D/3D with different grid spacings:

```
1/dt_adv = |v_x|/dx + |v_y|/dy + |v_z|/dz

1/dt_diff = D × (1/dx² + 1/dy² + 1/dz²)
```

## Specific Physics Cases

### Advection-Diffusion

```
∂u/∂t + v·∇u = D∇²u

Two limits apply:
- CFL: dt ≤ dx / |v|
- Fourier: dt ≤ dx² / (2D)

Peclet number: Pe = |v| × L / D
- Pe >> 1: advection-dominated, CFL limits
- Pe << 1: diffusion-dominated, Fourier limits
- Pe ~ 1: both matter, use minimum
```

### Phase-Field with Diffusion

```
Allen-Cahn: ∂φ/∂t = M(ε²∇²φ - f'(φ))

Diffusion limit: dt ≤ dx² / (2Mε²)
Reaction limit: dt ≤ 1 / (M × max|f''(φ)|)

Interface width: W ~ ε
Resolution: dx ≤ W/3 for 3 points in interface
```

### Navier-Stokes (Incompressible)

```
Advection: dt_adv ≤ dx / |u_max|
Viscous: dt_visc ≤ dx² / (2ν)

Re = |u|L/ν
- High Re: dt_adv dominates
- Low Re: dt_visc may dominate (unusual for simulations)
```

### Elastodynamics

```
Wave speed: c = sqrt(E/ρ) or sqrt((λ+2μ)/ρ)

CFL: dt ≤ dx / c

For explicit schemes, this is typically the limiting factor.
```

## Practical Computation

### Algorithm for dt Selection

```python
def compute_dt_limit(physics_params, grid):
    """Compute stable time step from all constraints."""
    limits = []

    # Advection
    if 'velocity' in physics_params:
        v_max = np.max(np.abs(physics_params['velocity']))
        dx_min = grid.min_spacing()
        dt_adv = 0.8 * dx_min / v_max
        limits.append(('advection', dt_adv))

    # Diffusion
    if 'diffusivity' in physics_params:
        D = physics_params['diffusivity']
        dx_min = grid.min_spacing()
        dim = grid.dimension
        dt_diff = 0.4 * dx_min**2 / (dim * D)
        limits.append(('diffusion', dt_diff))

    # Reaction
    if 'reaction_rate' in physics_params:
        k_max = physics_params['reaction_rate']
        dt_react = 0.5 / k_max
        limits.append(('reaction', dt_react))

    # Find limiting constraint
    dt_limit = min(dt for name, dt in limits)
    limiting = [name for name, dt in limits if dt == dt_limit][0]

    return dt_limit, limiting, limits
```

### Adaptive Recomputation

When to recompute dt:

| Event | Recompute? | Reason |
|-------|------------|--------|
| Start of simulation | Yes | Initial conditions |
| Each time step | Optional | If parameters change |
| Mesh refinement | Yes | dx changed |
| Material change | Yes | D, k changed |
| Large velocity change | Yes | CFL may change |

### Anisotropic Meshes

For grids with dx ≠ dy ≠ dz:

```
CFL (advection):
dt_adv ≤ 1 / (|v_x|/dx + |v_y|/dy + |v_z|/dz)

Fourier (diffusion):
dt_diff ≤ 0.5 / (D × (1/dx² + 1/dy² + 1/dz²))
```

**Critical:** Use the smallest spacing, not the average!

## Special Situations

### Locally Varying Properties

When D, v, or k vary in space:

```
Use maximum over all cells:
v_max = max over cells of |v(x)|
D_max = max over cells of D(x)
```

Or use local time stepping (more advanced).

### Implicit-Explicit (IMEX) Methods

When some terms are implicit:

```
Explicit part: CFL limit applies
Implicit part: No stability limit

Example (advection-diffusion):
- Implicit diffusion: removes dt_diff constraint
- Explicit advection: CFL still applies

dt ≤ dx / |v|  (only advection matters)
```

### Adaptive Mesh Refinement (AMR)

With multiple mesh levels:

```
Level 0: dx_0, dt_0 (coarse)
Level 1: dx_1 = dx_0/2, dt_1 = dt_0/2
Level 2: dx_2 = dx_0/4, dt_2 = dt_0/4
...

Subcycling: Fine levels take multiple steps per coarse step.
```

## Common Pitfalls

### Pitfall 1: Forgetting a Limit

```
Problem: Only checked CFL, forgot Fourier limit
Result: Instability when diffusion dominates
Fix: Always compute ALL relevant limits
```

### Pitfall 2: Using Average Instead of Minimum

```
Problem: dt = (dt_adv + dt_diff) / 2
Result: Too large dt, instability
Fix: dt = min(dt_adv, dt_diff)
```

### Pitfall 3: Ignoring Anisotropy

```
Problem: Used dx_avg instead of min(dx, dy, dz)
Result: dt too large in refined direction
Fix: Use minimum spacing in each limit formula
```

### Pitfall 4: Not Recomputing After Changes

```
Problem: Fixed dt throughout simulation
Result: Instability when parameters change
Fix: Recompute dt when physics changes significantly
```

## Diagnostics and Monitoring

### Courant Number Monitoring

```
C = v × dt / dx

Should be: C ≤ C_max (typically ~0.5-0.9)

If C > 1: Likely unstable
If C << 0.1: Inefficient, could use larger dt
```

### Fourier Number Monitoring

```
F = D × dt / dx²

Should be: F ≤ F_max (typically ~0.25-0.5 in 2D)

If F > 0.5/dim: Likely unstable (explicit)
If F << 0.01: Very conservative, check if needed
```

### Stability Margin

```
margin = dt_limit / dt_used

Healthy: margin ≥ 1.1 (10% safety)
Warning: margin = 1.0-1.1 (borderline)
Danger: margin < 1.0 (unstable!)
```

## Quick Reference

### Common Limits Table

| Physics | Limit Formula | Typical C |
|---------|---------------|-----------|
| Advection | C × dx / v | 0.5-0.9 |
| Diffusion (1D) | C × dx² / D | 0.5 |
| Diffusion (2D) | C × dx² / D | 0.25 |
| Diffusion (3D) | C × dx² / D | 0.167 |
| Sound waves | dx / c | 0.9 |
| Stiff reaction | 1 / λ_max | 0.1-0.5 |
| Phase-field | dx² / (Mε²) | 0.1-0.25 |

### Combined Example

```
Problem: 2D advection-diffusion with reaction
v = 1.0 m/s, D = 0.01 m²/s, k = 100 /s
dx = 0.01 m

dt_adv = 0.8 × 0.01 / 1.0 = 0.008 s
dt_diff = 0.25 × 0.01² / 0.01 = 0.0025 s
dt_react = 0.5 / 100 = 0.005 s

dt_limit = min(0.008, 0.0025, 0.005) = 0.0025 s
Limiting factor: diffusion
```

