@@ -19,7 +19,8 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
19
19
cmap = None , norm = None , arrowsize = 1 , arrowstyle = '-|>' ,
20
20
minlength = 0.1 , transform = None , zorder = None , start_points = None ,
21
21
maxlength = 4.0 , integration_direction = 'both' ,
22
- broken_streamlines = True , * , num_arrows = 1 ):
22
+ broken_streamlines = True , * , integration_max_step_scale = 1.0 ,
23
+ integration_max_error_scale = 1.0 , num_arrows = 1 ):
23
24
"""
24
25
Draw streamlines of a vector flow.
25
26
@@ -73,6 +74,24 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
73
74
If False, forces streamlines to continue until they
74
75
leave the plot domain. If True, they may be terminated if they
75
76
come too close to another streamline.
77
+ integration_max_step_scale : float, default: 1.0
78
+ Multiplier on the maximum allowable step in the streamline integration routine.
79
+ A value between zero and one results in a max integration step smaller than
80
+ the default max step, resulting in more accurate streamlines at the cost
81
+ of greater computation time; a value greater than one does the converse. Must be
82
+ greater than zero.
83
+
84
+ .. versionadded:: 3.11
85
+
86
+ integration_max_error_scale : float, default: 1.0
87
+ Multiplier on the maximum allowable error in the streamline integration routine.
88
+ A value between zero and one results in a tighter max integration error than
89
+ the default max error, resulting in more accurate streamlines at the cost
90
+ of greater computation time; a value greater than one does the converse. Must be
91
+ greater than zero.
92
+
93
+ .. versionadded:: 3.11
94
+
76
95
num_arrows : int
77
96
Number of arrows per streamline. The arrows are spaced equally along the steps
78
97
each streamline takes. Note that this can be different to being spaced equally
@@ -97,6 +116,18 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
97
116
mask = StreamMask (density )
98
117
dmap = DomainMap (grid , mask )
99
118
119
+ if integration_max_step_scale <= 0.0 :
120
+ raise ValueError (
121
+ "The value of integration_max_step_scale must be > 0, " +
122
+ f"got { integration_max_step_scale } "
123
+ )
124
+
125
+ if integration_max_error_scale <= 0.0 :
126
+ raise ValueError (
127
+ "The value of integration_max_error_scale must be > 0, " +
128
+ f"got { integration_max_error_scale } "
129
+ )
130
+
100
131
if num_arrows < 0 :
101
132
raise ValueError (f"The value of num_arrows must be >= 0, got { num_arrows = } " )
102
133
@@ -159,7 +190,9 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
159
190
for xm , ym in _gen_starting_points (mask .shape ):
160
191
if mask [ym , xm ] == 0 :
161
192
xg , yg = dmap .mask2grid (xm , ym )
162
- t = integrate (xg , yg , broken_streamlines )
193
+ t = integrate (xg , yg , broken_streamlines ,
194
+ integration_max_step_scale ,
195
+ integration_max_error_scale )
163
196
if t is not None :
164
197
trajectories .append (t )
165
198
else :
@@ -187,7 +220,8 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
187
220
xg = np .clip (xg , 0 , grid .nx - 1 )
188
221
yg = np .clip (yg , 0 , grid .ny - 1 )
189
222
190
- t = integrate (xg , yg , broken_streamlines )
223
+ t = integrate (xg , yg , broken_streamlines , integration_max_step_scale ,
224
+ integration_max_error_scale )
191
225
if t is not None :
192
226
trajectories .append (t )
193
227
@@ -480,7 +514,8 @@ def backward_time(xi, yi):
480
514
dxi , dyi = forward_time (xi , yi )
481
515
return - dxi , - dyi
482
516
483
- def integrate (x0 , y0 , broken_streamlines = True ):
517
+ def integrate (x0 , y0 , broken_streamlines = True , integration_max_step_scale = 1.0 ,
518
+ integration_max_error_scale = 1.0 ):
484
519
"""
485
520
Return x, y grid-coordinates of trajectory based on starting point.
486
521
@@ -500,14 +535,18 @@ def integrate(x0, y0, broken_streamlines=True):
500
535
return None
501
536
if integration_direction in ['both' , 'backward' ]:
502
537
s , xyt = _integrate_rk12 (x0 , y0 , dmap , backward_time , maxlength ,
503
- broken_streamlines )
538
+ broken_streamlines ,
539
+ integration_max_step_scale ,
540
+ integration_max_error_scale )
504
541
stotal += s
505
542
xy_traj += xyt [::- 1 ]
506
543
507
544
if integration_direction in ['both' , 'forward' ]:
508
545
dmap .reset_start_point (x0 , y0 )
509
546
s , xyt = _integrate_rk12 (x0 , y0 , dmap , forward_time , maxlength ,
510
- broken_streamlines )
547
+ broken_streamlines ,
548
+ integration_max_step_scale ,
549
+ integration_max_error_scale )
511
550
stotal += s
512
551
xy_traj += xyt [1 :]
513
552
@@ -524,7 +563,9 @@ class OutOfBounds(IndexError):
524
563
pass
525
564
526
565
527
- def _integrate_rk12 (x0 , y0 , dmap , f , maxlength , broken_streamlines = True ):
566
+ def _integrate_rk12 (x0 , y0 , dmap , f , maxlength , broken_streamlines = True ,
567
+ integration_max_step_scale = 1.0 ,
568
+ integration_max_error_scale = 1.0 ):
528
569
"""
529
570
2nd-order Runge-Kutta algorithm with adaptive step size.
530
571
@@ -550,7 +591,7 @@ def _integrate_rk12(x0, y0, dmap, f, maxlength, broken_streamlines=True):
550
591
# This error is below that needed to match the RK4 integrator. It
551
592
# is set for visual reasons -- too low and corners start
552
593
# appearing ugly and jagged. Can be tuned.
553
- maxerror = 0.003
594
+ maxerror = 0.003 * integration_max_error_scale
554
595
555
596
# This limit is important (for all integrators) to avoid the
556
597
# trajectory skipping some mask cells. We could relax this
@@ -559,6 +600,7 @@ def _integrate_rk12(x0, y0, dmap, f, maxlength, broken_streamlines=True):
559
600
# nature of the interpolation, this doesn't boost speed by much
560
601
# for quite a bit of complexity.
561
602
maxds = min (1. / dmap .mask .nx , 1. / dmap .mask .ny , 0.1 )
603
+ maxds *= integration_max_step_scale
562
604
563
605
ds = maxds
564
606
stotal = 0
0 commit comments