Point-pairs search with fixed cutoff distance (direct) #
Description #
I work with point pair searches through particle-based simulations (mostly SPH and DEM). The algorithm here is the most basic way to perform a pair search. It is O(N2) time, so is not useful for any practical applications.
I use it frequently to server as a reference when investigating other ways to search for pairs. It’s simple to code, so is harder to introduce conceptual and coding errors.
It does also have some real-world relevance, as the cell-list pair-search algorithm uses many smaller direct searches.
The Fortran code below contains the module dsearch_m
, and a main
program. To compile only the module, pass the -DNOMAIN
option to the compiler, and the preprocessor will omit it.
The module has the dsearch
and dsearch_compact
interfaces. dsearch
returns two lists, one with the “left-sided” points of the pairs, and pair_j
, which returns the “right-sided” points in the pair list. In contrast, dsearch_compact
returns a shorter list, endpos
, where endpos(i)
corresponds to the last pair with point i
as the “left-sided” point. See the main
program for an example of how to iterate over the lists.
The benefit of dsearch_compact
is the output format has a smaller memory footprint, and iterating over the list requires fewer memory accesses and is consequently usually a bit faster. However, the drawback is that it is less intuitive and more awkward to iterate over.
Code (Fortran) #
! Module to perform direct search for pairs of points within a fixed cutoff.
! dsearch subroutine
! dim: An integer defining dimension of the coordinates.
! npoints: An integer with number of points.
! x: A real/double precision array of dim x points dimension containing the list of points to find pairs of.
! cutoff: The distance (same type as x) which defines a pair
! maxnpair: An integer of the maximum number of pairs expected.
! npairs: An integer with the number of pairs found.
! pair_i: An integer array of maxnpairs length with the "left-sided" point in a pair.
! pair_j: An integer array of maxnpairs length with the "right-sided" point in a pair.
! dsearch_compact subroutine
! same as above, except except pair_i is replaced with endpos: a list of endlocations of the left-sided point in a
! pair. The main program shows an example of how to iterate through along the list.
module dsearch_m
public
#ifdef SINGLEPRECISION
parameter, integer:: f = kind(1.)
#else
parameter, integer:: f = kind(1.d0)
#endif
contains
!---------------------------------------------------------------------------
subroutine dsearch_dp(dim, npoints, x, cutoff, maxnpair, npairs, pair_i, pair_j)
! subroutine to find pairs of points using direct search. Output is
! two lists describing which points are in the pair
implicit none
integer, intent(in):: dim, npoints, maxnpair
real(f), intent(in):: x(dim, npoints), cutoff
integer, intent(out):: npairs, pair_i(maxnpair), pair_j(maxnpair)
integer:: i, j
real(f):: xi(dim), r2
npairs = 0
do i = 1, npoints - 1
xi(:) = x(:, i)
do j = i + 1, npoints
r2 = sum((xi(:) - x(:, j))**2)
if (r2 <= cutoff**2) then
npairs = npairs + 1
pair_i(npairs) = i
pair_j(npairs) = j
end if
end do
end do
end subroutine dsearch_dp
!---------------------------------------------------------------------------
subroutine dsearch_compact_dp(dim, npoints, x, cutoff, maxnpair, npairs, endpos, pair_j)
! subroutine to find pairs using direct search. Output is two lists. One
! list describes the last pair with point `i` as the "left-sided" point.
implicit none
integer, intent(in):: dim, npoints, maxnpair
real(f), intent(in):: x(dim, npoints), cutoff
integer, intent(out):: endpos(npoints), npairs, pair_j(maxnpair)
integer:: i, j
real(f):: xi(dim), r2
npairs = 0
do i = 1, npoints
xi(:) = x(:, i)
do j = i + 1, npoints
r2 = sum((xi(:) - x(:, j))**2)
if (r2 <= cutoff**2) then
npairs = npairs + 1
pair_j(npairs) = j
end if
end do
! update end position when done with point i
endpos(i) = npairs
end do
end subroutine dsearch_compact_dp
end module dsearch_m
#ifndef NOMAIN
program main
use dsearch_m, only: f, dsearch, dsearch_compact
implicit none
integer, parameter:: n = 100, dim = 3, maxnpair = 60*n ! estimated using
! 2x the coaxial spacing if the points were arranged in a square
real(f), parameter:: cutoff = 2*n**(-1.d0/dim)
real(f):: x(dim, n)
integer:: pair_i(maxnpair), pair_j(maxnpair), npairs, startpos, i, j, k, endpos(n)
! initialize positions with pseudo-random numbers
call random_number(x)
! finding pairs
call dsearch(dim, n, x, cutoff, maxnpair, npairs, pair_i, pair_j)
write (*, '(A)') 'Executing "normal" dsearch'
write (*, '(2x, A,I4,A)') 'Found ', n, ' pairs'
write (*, '(2x, A)') 'First and last 5 pairs of points found:'
write (*, '(2x, 4(A4, 1x))') 'Pair', 'i', 'j'
do k = 1, npairs
if (k <= 5 .or. k > npairs - 4) write (*, '(2x, 3(I4, 1x))') k, pair_i(k), pair_j(k)
if (k == 6) write (*, '(2x, A)') '...'
end do
write (*, *)
write (*, '(A)') 'Executing "compact" dsearch'
write (*, '(2x, A,I4,A)') 'Found ', n, ' pairs'
write (*, '(2x, A)') 'First and last 5 pairs of points found:'
write (*, '(2x, 4(A4, 1x))') 'Pair', 'i', 'j'
call dsearch_compact(dim, n, x, cutoff, maxnpair, npairs, endpos, pair_j)
do i = 1, n
if (i == 1) startpos = 1
if (i > 1) startpos = endpos(i - 1) + 1
do k = startpos, endpos(i)
if (k <= 5 .or. k > npairs - 4) write (*, '(2x, 3(I4,1x))') k, i, pair_j(k)
if (k == 6) write (*, '(2x, A)') '...'
end do
end do
end program main
#endif