@@ -19,7 +19,8 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
1919 cmap = None , norm = None , arrowsize = 1 , arrowstyle = '-|>' ,
2020 minlength = 0.1 , transform = None , zorder = None , start_points = None ,
2121 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 ):
2324 """
2425 Draw streamlines of a vector flow.
2526
@@ -73,6 +74,24 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
7374 If False, forces streamlines to continue until they
7475 leave the plot domain. If True, they may be terminated if they
7576 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+
7695 num_arrows : int
7796 Number of arrows per streamline. The arrows are spaced equally along the steps
7897 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,
97116 mask = StreamMask (density )
98117 dmap = DomainMap (grid , mask )
99118
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+
100131 if num_arrows < 0 :
101132 raise ValueError (f"The value of num_arrows must be >= 0, got { num_arrows = } " )
102133
@@ -159,7 +190,9 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
159190 for xm , ym in _gen_starting_points (mask .shape ):
160191 if mask [ym , xm ] == 0 :
161192 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 )
163196 if t is not None :
164197 trajectories .append (t )
165198 else :
@@ -187,7 +220,8 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
187220 xg = np .clip (xg , 0 , grid .nx - 1 )
188221 yg = np .clip (yg , 0 , grid .ny - 1 )
189222
190- t = integrate (xg , yg , broken_streamlines )
223+ t = integrate (xg , yg , broken_streamlines , integration_max_step_scale ,
224+ integration_max_error_scale )
191225 if t is not None :
192226 trajectories .append (t )
193227
@@ -480,7 +514,8 @@ def backward_time(xi, yi):
480514 dxi , dyi = forward_time (xi , yi )
481515 return - dxi , - dyi
482516
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 ):
484519 """
485520 Return x, y grid-coordinates of trajectory based on starting point.
486521
@@ -500,14 +535,18 @@ def integrate(x0, y0, broken_streamlines=True):
500535 return None
501536 if integration_direction in ['both' , 'backward' ]:
502537 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 )
504541 stotal += s
505542 xy_traj += xyt [::- 1 ]
506543
507544 if integration_direction in ['both' , 'forward' ]:
508545 dmap .reset_start_point (x0 , y0 )
509546 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 )
511550 stotal += s
512551 xy_traj += xyt [1 :]
513552
@@ -524,7 +563,9 @@ class OutOfBounds(IndexError):
524563 pass
525564
526565
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 ):
528569 """
529570 2nd-order Runge-Kutta algorithm with adaptive step size.
530571
@@ -550,7 +591,7 @@ def _integrate_rk12(x0, y0, dmap, f, maxlength, broken_streamlines=True):
550591 # This error is below that needed to match the RK4 integrator. It
551592 # is set for visual reasons -- too low and corners start
552593 # appearing ugly and jagged. Can be tuned.
553- maxerror = 0.003
594+ maxerror = 0.003 * integration_max_error_scale
554595
555596 # This limit is important (for all integrators) to avoid the
556597 # trajectory skipping some mask cells. We could relax this
@@ -559,6 +600,7 @@ def _integrate_rk12(x0, y0, dmap, f, maxlength, broken_streamlines=True):
559600 # nature of the interpolation, this doesn't boost speed by much
560601 # for quite a bit of complexity.
561602 maxds = min (1. / dmap .mask .nx , 1. / dmap .mask .ny , 0.1 )
603+ maxds *= integration_max_step_scale
562604
563605 ds = maxds
564606 stotal = 0
0 commit comments