Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0657e73

Browse files
committed
Add improvements from Tom Flannaghan.
* Add faster adaptive-step algorithm (RK12) to replace RK45 and RK4 integrators. * Fix arrow placement when streamline is only 2 points. * Calculate stepsize based on grid size.
1 parent 687af18 commit 0657e73

1 file changed

Lines changed: 115 additions & 4 deletions

File tree

lib/matplotlib/streamplot.py

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,9 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=1, color='k', cmap=None,
136136
## Add arrows half way along each trajectory.
137137
s = np.cumsum(np.sqrt(np.diff(tx)**2 + np.diff(ty)**2))
138138
n = np.searchsorted(s, s[-1] / 2.)
139-
p = mpp.FancyArrowPatch((tx[n], ty[n]), (tx[n+1], ty[n+1]), **arrow_kw)
139+
arrow_tail = (tx[n], ty[n])
140+
arrow_head = (np.mean(tx[n:n+2]), np.mean(ty[n:n+2]))
141+
p = mpp.FancyArrowPatch(arrow_tail, arrow_head, **arrow_kw)
140142
axes.add_patch(p)
141143

142144
axes.update_datalim(((x.min(), y.min()), (x.max(), y.max())))
@@ -297,6 +299,12 @@ def _update_trajectory(self, xm, ym):
297299
class InvalidIndexError(Exception):
298300
pass
299301

302+
class OutOfBounds(Exception):
303+
pass
304+
305+
class Terminated(Exception):
306+
pass
307+
300308

301309
# Integrator definitions
302310
#========================
@@ -326,6 +334,8 @@ def backward_time(xi, yi):
326334
_integrate = _rk4
327335
elif integrator == 'RK45':
328336
_integrate = _rk45
337+
elif integrator == 'RK12':
338+
_integrate = _rk12
329339

330340
def rk4_integrate(x0, y0):
331341
"""Return x, y coordinates of trajectory based on starting point.
@@ -353,10 +363,112 @@ def rk4_integrate(x0, y0):
353363

354364
return rk4_integrate
355365

366+
## I have added this RK12 integrator, which I think supercedes both
367+
## other integrators. The rationale is two-fold:
368+
##
369+
## 1. To get decent looking trajectories and to sample every mask cell
370+
## on the trajectory we need a small timestep, so a lower order
371+
## solver doesn't hurt us unless the data is *very* high resolution.
372+
## In fact, for cases where the user inputs
373+
## data smaller or of similar grid size to the mask grid, the higher
374+
## order corrections are negligable because of the v fast linear
375+
## interpolation used in interpgrid.
376+
##
377+
## 2. For high resolution input data (i.e. beyond the mask
378+
## resolution), we must reduce the timestep. Therefore, an adaptive
379+
## timestep is more suited to the problem as this would be very hard
380+
## to judge automatically otherwise.
381+
##
382+
## This integrator is about 1.5 - 2x as fast as both the RK4 and RK45
383+
## solvers in most setups on my machine. I would recommend removing the
384+
## other two to keep things simple.
385+
386+
def _rk12(x0, y0, dmap, f):
387+
## This error is below that needed to match the RK4 integrator. It
388+
## is set for visual reasons -- too low and corners start
389+
## appearing ugly and jagged. Can be tuned.
390+
maxerror = 0.003
391+
392+
## This limit is important (for all integrators) to avoid the
393+
## trajectory skipping some mask cells. We could relax this
394+
## condition if we use the code which is commented out below to
395+
## increment the location gradually. However, due to the efficient
396+
## nature of the interpolation, this doesn't boost speed by much
397+
## for quite a bit of complexity.
398+
maxds = min(1./dmap.mask.nx, 1./dmap.mask.ny, 0.1)
399+
400+
ds = maxds
401+
stotal = 0
402+
xi = x0
403+
yi = y0
404+
xf_traj = []
405+
yf_traj = []
406+
407+
try:
408+
while dmap.grid.valid_index(xi, yi):
409+
xf_traj.append(xi)
410+
yf_traj.append(yi)
411+
try:
412+
k1x, k1y = f(xi, yi)
413+
k2x, k2y = f(xi + ds * k1x,
414+
yi + ds * k1y)
415+
except IndexError:
416+
# Out of the domain on one of the intermediate steps
417+
raise OutOfBounds
418+
419+
dx1 = ds * k1x
420+
dy1 = ds * k1y
421+
dx2 = ds * 0.5 * (k1x + k2x)
422+
dy2 = ds * 0.5 * (k1y + k2y)
423+
424+
nx, ny = dmap.grid.shape
425+
# Error is normalized to the axes coordinates
426+
error = np.sqrt(((dx2-dx1)/nx)**2 + ((dy2-dy1)/ny)**2)
427+
428+
# Only save step if within error tolerance
429+
if error < maxerror:
430+
xi += dx2
431+
yi += dy2
432+
try:
433+
dmap.update_trajectory(xi, yi)
434+
except InvalidIndexError:
435+
raise Terminated
436+
if (stotal + ds) > 2:
437+
raise Terminated
438+
stotal += ds
439+
440+
# recalculate stepsize based on Runge-Kutta-Fehlberg method
441+
ds = min(maxds, 0.85 * ds * (maxerror/error)**0.2)
442+
443+
except OutOfBounds:
444+
## This is raised when the edge of the domain is
445+
## reached. Here, a simple Euler integration is used up to the
446+
## boundary of the domain, improving the neatness of the
447+
## figure.
448+
nx, ny = dmap.grid.shape
449+
xi = xf_traj[-1] # in data coordinates
450+
yi = yf_traj[-1]
451+
cx, cy = f(xi, yi) # ds.cx is in data coordinates, ds in axis coord.
452+
if cx > 0:
453+
dsx = (nx - 1 - xi) / cx
454+
else:
455+
dsx = xi / -cx
456+
if cy > 0:
457+
dsy = (ny - 1 - yi) / cy
458+
else:
459+
dsy = yi / -cy
460+
ds = min(dsx, dsy)
461+
xf_traj.append(xi+cx*ds)
462+
yf_traj.append(yi+cy*ds)
463+
stotal += ds
464+
except Terminated:
465+
pass
466+
467+
return stotal, xf_traj, yf_traj
356468

357469
def _rk4(x0, y0, dmap, f):
358470
"""4th-order Runge-Kutta algorithm with fixed step size"""
359-
ds = 0.01 #min(1./grid.ny, 1./grid.ny, 0.01)
471+
ds = min(1./dmap.mask.nx, 1./dmap.mask.ny, 0.01)
360472
stotal = 0
361473
xi = x0
362474
yi = y0
@@ -394,7 +506,7 @@ def _rk4(x0, y0, dmap, f):
394506
def _rk45(x0, y0, dmap, f):
395507
"""5th-order Runge-Kutta algorithm with adaptive step size"""
396508
maxerror = 0.001
397-
maxds = 0.03
509+
maxds = min(1./dmap.mask.nx, 1./dmap.mask.ny, 0.03)
398510
ds = maxds
399511
stotal = 0
400512
xi = x0
@@ -457,7 +569,6 @@ def _rk45(x0, y0, dmap, f):
457569
ds = min(maxds, 0.85 * ds * (maxerror/error)**0.2)
458570
return stotal, xf_traj, yf_traj
459571

460-
461572
# Utility functions
462573
#========================
463574

0 commit comments

Comments
 (0)