2nd-order Runge-Kutta algorithm with adaptive step size. This method is also referred to as the improved Euler's method, or Heun's method. This method is favored over higher-order methods because: 1. To get decent looking trajectories and to sample every mask cell on the tr
(x0, y0, dmap, f, maxlength, broken_streamlines=True,
integration_max_step_scale=1.0,
integration_max_error_scale=1.0)
| 564 | |
| 565 | |
| 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): |
| 569 | """ |
| 570 | 2nd-order Runge-Kutta algorithm with adaptive step size. |
| 571 | |
| 572 | This method is also referred to as the improved Euler's method, or Heun's |
| 573 | method. This method is favored over higher-order methods because: |
| 574 | |
| 575 | 1. To get decent looking trajectories and to sample every mask cell |
| 576 | on the trajectory we need a small timestep, so a lower order |
| 577 | solver doesn't hurt us unless the data is *very* high resolution. |
| 578 | In fact, for cases where the user inputs |
| 579 | data smaller or of similar grid size to the mask grid, the higher |
| 580 | order corrections are negligible because of the very fast linear |
| 581 | interpolation used in `interpgrid`. |
| 582 | |
| 583 | 2. For high resolution input data (i.e. beyond the mask |
| 584 | resolution), we must reduce the timestep. Therefore, an adaptive |
| 585 | timestep is more suited to the problem as this would be very hard |
| 586 | to judge automatically otherwise. |
| 587 | |
| 588 | This integrator is about 1.5 - 2x as fast as RK4 and RK45 solvers (using |
| 589 | similar Python implementations) in most setups. |
| 590 | """ |
| 591 | # This error is below that needed to match the RK4 integrator. It |
| 592 | # is set for visual reasons -- too low and corners start |
| 593 | # appearing ugly and jagged. Can be tuned. |
| 594 | maxerror = 0.003 * integration_max_error_scale |
| 595 | |
| 596 | # This limit is important (for all integrators) to avoid the |
| 597 | # trajectory skipping some mask cells. We could relax this |
| 598 | # condition if we use the code which is commented out below to |
| 599 | # increment the location gradually. However, due to the efficient |
| 600 | # nature of the interpolation, this doesn't boost speed by much |
| 601 | # for quite a bit of complexity. |
| 602 | maxds = min(1. / dmap.mask.nx, 1. / dmap.mask.ny, 0.1) |
| 603 | maxds *= integration_max_step_scale |
| 604 | |
| 605 | ds = maxds |
| 606 | stotal = 0 |
| 607 | xi = x0 |
| 608 | yi = y0 |
| 609 | xyf_traj = [] |
| 610 | |
| 611 | while True: |
| 612 | try: |
| 613 | if dmap.grid.within_grid(xi, yi): |
| 614 | xyf_traj.append((xi, yi)) |
| 615 | else: |
| 616 | raise OutOfBounds |
| 617 | |
| 618 | # Compute the two intermediate gradients. |
| 619 | # f should raise OutOfBounds if the locations given are |
| 620 | # outside the grid. |
| 621 | k1x, k1y = f(xi, yi) |
| 622 | k2x, k2y = f(xi + ds * k1x, yi + ds * k1y) |
| 623 |
no test coverage detected
searching dependent graphs…