This project simulates the dynamics of the tippet and nymph fished euro-style in a river current. It uses a physics-based approach to model the interaction between the water flow, the tippet and the nymph.
NOTE: These simulations have NOT been experimentally verified. So while the trends (directions of the effects) likely are correct the absolute values may not be. At this point, the only verification has been by comparing with results from simulations at Fly Fishing Reserach -- a site I highly recommend for those enjoying a theoretical approach to understanding aspects of fly fishing.
These two figures are generated by the example script below.
Notice how the fly moves faster than the surrounding water, illustrated by the movement of the short, blue line segments. The reason for this is discussed below, under drift velocity.
With this type of simulation, we can study how the drift behaves under different conditions. For example, what happens to the depth and speed of the fly when we move the rod tip slower than the surface water?
In the above simulations we can see the effect the speed of the rod tip has on the drift. The most "natural" drift is achieved when the rod moves at 80% of the surface velocity; the fly sinks deepest and best matches the speed of the bottom layer, though it still moves significantly faster.
As clearly demonstrated in Modern Nymphing: European Inspired Techniques by Lance Egan and Devin Olsen, the surface water flows much faster than the bottom water. This impacts the drift of a nymph close to the bottom.
The drift of a nymph is governed by the competition between the local forces on the fly (which try to move it at the speed of the water surrounding the fly) and the drag forces on the tippet (which is continuously pulled downstream by the faster surface currents). This dynamic is analogous to towed cable systems described in the literature, where the shape and depth of the line are dictated by the balance of hydrodynamic drag, tension, and gravity [Spolek, 2007].
- Drift Velocity:
In scenarios when the nymph is not corrected and drifted "dead drift", the nymph will drift faster than the bottom water around it. This is because the tippet pass through the higher-velocity upper water column, acting like a sail that pulls the nymph downstream. This mirrors the behavior of towed systems where drag forces on the cable drive the configuration [Spolek, 2007].
-
Influence of Parameters:
-
Nymph Weight (0.1 - 0.5g): Heavier objects sink faster and maintain depth better against drag forces. In towed systems, specific gravity is a primary determinant of the sinking angle and depth. Heavier nymphs reach the slower boundary layer sooner, where inertia helps retard the downstream pull.
-
Tippet Diameter (0.1-0.3mm): Thicker tippets have more surface area (
$A_{projected} = \text{diameter} \times \text{length}$ ), experiencing significantly higher drag from the fast surface water. This increases the unnatural downstream speed of the nymph. Thin tippets are critical for achieving a true dead drift that matches the bottom speed. -
Fly Drag: A "bushy" fly has a high drag coefficient (
$C_D$ ). While this helps it move with the local water, it also creates more resistance against sinking, keeping the fly in faster water for longer.
To simulate this, we treat the system as a Tethered Underwater System, similar to the cable-towing models described in the literature (e.g., Dowling, Spolek), but with the "towing" force replaced by the distributed drag of the river current.
Rivers do not flow at a uniform speed. Friction with the riverbed causes the water to slow down significantly near the bottom. Standard fluid dynamics models the friction with the riverbed using the Logarithmic Law of the Wall:
-
$u(y)$ : Water velocity at height$y$ from the bottom. -
$u_*$ : Shear velocity (related to river slope and depth). -
$\kappa$ : Von Kármán constant ($\approx 0.41$ ). -
$z_0$ : Roughness height (determined by rock size on the riverbed).
A power-law approximation is often computationally faster and sufficient:
where
The nymph is modeled as a point mass or small sphere subjected to three forces [Yang et al (2022)]:
Graviy (
Buoyancy (
Hydrodynamic Drag (
Drag Coefficient (
The "Bushiness" Multiplier (
To differentiate between a slim Perdigon and a fluffy Woolly Bugger:
-
$\beta = 1.0$ : Smooth bead/Epoxy fly. -
$\beta = 1.5 - 2.0$ : Standard Nymph (Pheasant Tail). -
$\beta > 3.0$ : Large stonefly or streamer.
The tippet is a flexible cylinder spanning from the surface to the fly. It experiences distributed drag that varies with depth. Following the Morison Equation approach seen in cable dynamics literature, the force per unit length is split into normal and tangential components [Dowling (1987); Dowling (1988); Yang (2022)].
Both Normal Drag (
-
$d_{tippet}$ : Tippet diameter. -
$C_{D,norm} \approx 1.2$ ,$C_{D,tan} \approx 0.01$ .
Key Insight: Since
Anglers often manipulate the speed of the rod tip relative to the surface water. This changes the Towing Velocity (
- Factor < 1 (Holding Back): The rod tip moves slower than the surface current. This is common in Euro nymphing techniques. By slowing the top of the line, the angler attempts to counteract the downstream belly, though it can also lift the fly due to increased tension [Spolek (2007)].
-
Factor > 1 (Towing): The rod tip moves faster than the current. The results is that the fly will be towed faster than the surrounding water and thus stay higher in the water column. The system behaves like the towed cable models in Dowling (1987; 1988), where the high towing speed (
$U$ ) increases the drag forces and lifts the cable (and fly) toward the surface. - Factor = 1 ("Dead Drift"): The baseline scenario where the rod tip tracks perfectly with the surface water. This is often called dead drift, but it's important to note that the nymph under this condition will drift faster than the surrounding water. That is, the nymph itself isn't in a dead drift as it will be trailing the rod tip.
The ROD_TIP_SPEED_FACTOR is a multiplier applied to the surface water velocity.
The most robust way to simulate this is the Lumped Mass Method. The tippet is divided into
-
Discretization: Divide the tippet into
$N$ segments connected by massless springs (representing elasticity) or rigid rods (if inextensible). The fluid forces are often integrated over these segments and applied to the nodes [Yang et al (2022)]. -
State Vector: Define the position
$(x_i, y_i)$ and velocity$(\dot{x}_i, \dot{y}_i)$ for every node$i$ . Node$0$ is the fly; Node$N$ is the rod tip/indicator at the surface. -
Force Calculation Loop:
- For each node
$i$ at height$y_i$ , calculate the local water velocity$u(y_i)$ . - Calculate Drag Force on the node based on
$u(y_i) - \dot{x}_i$ [Yang et al (2022)]. - Calculate Tension forces from neighbor nodes (
$i-1$ and$i+1$ ) based on Hooke's Law: $T = k(|\vec{r}_{i+1} - \vec{r}i| - L{seg})$ [Sjöberg (1991)]. - Add Gravity/Buoyancy [Dowling (1988)].
- For each node
- Time Stepping: Use a numerical integrator (e.g., Runge-Kutta 4 or Euler) to update positions [Sjöberg (1991); Ya
simulate.py: Contains the core physics engine (simulatefunction).utils.py: Contains utility functions for the simulation.visualize.py: Plots the simulation results and generates animated GIFs.
- Python 3.x
numpymatplotlibscipytqdm(for progress bar)
This project is designed to be used as a Python library where you import the simulation and visualization modules into your own scripts.
Use the simulate function from simulate.py. You can customize parameters like fly mass, tippet diameter, and rod handling.
The visualize.py module provides functions to plot static graphs (plot_drift) or generate animations (make_movie).
Here is a complete example of how to run a simulation and visualize the result:
import matplotlib.pyplot as plt
from simulate import simulate
from visualize import plot_drift, make_movie
from utils import calculate_sink_metrics
# --- 1. Run the Simulation ---
# Returns a dictionary containing position (x, y), velocity, and time data
drift_data = simulate(
fly_mass='3.0mm', # Fly bead size (or float in kg)
tippet_diameter='6x', # Tippet diameter (or float in m)
bugginess=1.0, # 1-4 scale of fly drag/bushiness
tippet_length=1.5, # Length of the tippet in meters
tippet_density='nylon', # Material density (or float in kg/m^3)
rod_tip_speed_factor=0.85, # Speed of rod tip relative to surface water (1.0 = dead drift)
river_depth=1.5, # Depth of the river in meters
surface_velocity=1.5, # Surface velocity in m/s
river_profile_exp=0.166, # Power-law exponent for velocity profile
time_span=15.0, # Duration of simulation in seconds
segments=20 # Number of segments in the tippet model
)
# --- 2. Visualize Results ---
# A. Print Metrics
metrics = calculate_sink_metrics(drift_data)
print(f"Max Drift Depth: {metrics['max_depth']:.2f} m")
print(f"Average Sink Rate: {metrics['average_sink_rate']:.2f} m/s")
# B. Static Plot
fig, ax = plt.subplots(figsize=(12, 6))
plot_drift(drift_data, ax=ax, title="Nymph Drift Profile")
plt.savefig("drift_result.png")
print("Saved drift_result.png")
# C. Create an Animation (GIF)
# Note: Requires ffmpeg or pillow
make_movie(
drifts=[drift_data],
filename='drift_animation.gif',
fps=15,
title=f"Drift Simulation (Max Depth: {metrics['max_depth']:.2f}m)"
)

