@@ -18,7 +18,7 @@ struct EmptyList <: NeighbourList end
1818
1919""" Construct an `EmptyList` (ignores box, rcut, N).
2020"""
21- function EmptyList (box, rcut, N)
21+ function EmptyList (box, rcut, N; list_parameters = nothing )
2222 return EmptyList ()
2323end
2424
114114
115115Cells are chosen so that their dimensions are at least `rcut`, and neighbour cells are precomputed.
116116"""
117- function CellList (box:: SVector{d,T} , rcut:: T , N:: Int ) where {d,T<: AbstractFloat }
117+ function CellList (box:: SVector{d,T} , rcut:: T , N:: Int ; list_parameters = nothing ) where {d,T<: AbstractFloat }
118118
119119 # Calculate cell dimensions ensuring they're >= rcut
120120 cell_box = @. box / floor (Int, box / rcut)
233233
234234""" Construct a `LinkedList` neighbour list given box, cutoff `rcut`, and number of particles `N`.
235235"""
236- function LinkedList (box, rcut, N)
236+ function LinkedList (box, rcut, N; list_parameters = nothing )
237237 cell = box ./ fld .(box, rcut)
238238 ncells = ntuple (a -> Int (box[a] / cell[a]), length (box))
239239 head = - ones (Int, prod (ncells))
@@ -375,3 +375,169 @@ function (linked_list::LinkedList)(system::Particles, i::Int)
375375
376376 return LinkedIterator (neighbour_cells, linked_list. head, linked_list. list)
377377end
378+
379+
380+ # Verlet list
381+
382+ """ Verlet-list neighbour list implementation.
383+
384+ Uses a cell list to find all particles within rcut + dr.
385+ Keeps an array of neighbour ids for each particle
386+ """
387+ struct VerletList{T<: AbstractFloat , d} <: NeighbourList
388+ cs:: Vector{Int}
389+ box:: SVector{d,T}
390+ ncells:: NTuple{d,Int}
391+ cells:: Vector{Vector{Int}} # List of particles in each cell
392+ rcut:: T
393+ dr:: T
394+ dr2o4:: T
395+ neighbours:: Vector{Vector{Int}}
396+ neighbour_cells:: Vector{Vector{Int}} # List of neighbouring cells
397+ positions_at_last_update:: Vector{SVector{d,T}}
398+ end
399+
400+ """ Construct a `VerletList` neighbour list given box, cutoff `rcut`, cutoff padding `dr`, and number of particles `N`.
401+ """
402+ function VerletList (box, rcut, N; list_parameters= nothing )
403+ if list_parameters == nothing || ! haskey (list_parameters, " dr" )
404+ error (" Verlet list must have dr as a parameter" )
405+ end
406+ dr = list_parameters[" dr" ]
407+ cell = box ./ fld .(box, rcut + dr)
408+ ncells = ntuple (a -> Int (box[a] / cell[a]), length (box))
409+ head = - ones (Int, prod (ncells))
410+ list = zeros (Int, N)
411+ cs = zeros (Int, N)
412+ neighbour_cells = build_neighbour_cells (ncells)
413+ neighbours = [Int[] for _ in 1 : N]
414+ positions_at_last_update = [zeros (SVector{length (box), typeof (box[1 ])}) for _ in 1 : N]
415+ cells = [Int[] for _ in 1 : prod (ncells)]
416+ return VerletList (cs, cell, ncells, cells, rcut, dr, (dr^ 2 )/ 4 , neighbours, neighbour_cells, positions_at_last_update)
417+ end
418+
419+ """ Calling a VerletList objects return an object which can be iterated upon.
420+
421+ This iteration will return the indices of the neighbours of particle i.
422+ """
423+ function (verlet_list:: VerletList )(:: Particles , i:: Int )
424+ return (j for j in verlet_list. neighbours[i])
425+ end
426+
427+ """ Populate `neighbour_list` (a `VerletList`) by constructing the list of neighbours for each particle.
428+
429+ These neighbours are all particles within a distance `rcut` + `dr`
430+ """
431+ function build_neighbour_list! (system:: Particles , neighbour_list:: VerletList )
432+ # Populate cell list
433+ for (i, position_i) in enumerate (system)
434+ c = get_cell_index (position_i, neighbour_list)
435+ neighbour_list. cs[i] = c
436+ push! (neighbour_list. cells[c], i) # Directly append particle index
437+ end
438+
439+ # Now iterate over all pairs i,j, and see if they are within the cutoff
440+ r_cutoff2 = (neighbour_list. rcut + neighbour_list. dr)^ 2
441+ for (i, position_i) in enumerate (system)
442+ c = get_cell_index (position_i, neighbour_list)
443+ neighbour_cells = neighbour_list. neighbour_cells[c]
444+ # Scan the neighbourhood of cell mc (including itself)
445+ # and from there scan atoms in cell c2
446+ for c2 in neighbour_cells
447+ @inbounds for j in neighbour_list. cells[c2]
448+ # Don't double count, or self interact
449+ if j <= i
450+ continue
451+ end
452+ position_j = get_position (system, j)
453+ r2 = nearest_image_distance_squared (position_i, position_j, get_box (system))
454+ if r2 < r_cutoff2
455+ push! (neighbour_list. neighbours[i], j)
456+ push! (neighbour_list. neighbours[j], i)
457+ end
458+ end
459+ end
460+
461+ neighbour_list. positions_at_last_update[i] = copy (position_i)
462+ end
463+
464+ return nothing
465+ end
466+
467+
468+ """ Update particle `i` moving from cell `c` to cell `c2` in a `VerletList`.
469+
470+ Performs an in-place removal from the old cell and appends to the new one.
471+ """
472+ function update_cell_list! (i, c, c2, neighbour_list:: VerletList )
473+ # Remove from old cell using in-place filter
474+ filter! (x -> x != i, neighbour_list. cells[c])
475+ # Add to new cell
476+ push! (neighbour_list. cells[c2], i)
477+ # Update particle's cell index
478+ neighbour_list. cs[i] = c2
479+ return nothing
480+ end
481+
482+ """ Return the old and new cell indices for particle `i` in `neighbour_list` (VerletList).
483+
484+ Used to detect whether a particle has moved between cells.
485+ """
486+ function old_new_cell (system:: Particles , i, neighbour_list:: VerletList )
487+ c = neighbour_list. cs[i]
488+ # New cell index
489+ mc2 = get_cell (get_position (system, i), neighbour_list)
490+ c2 = cell_index (neighbour_list, mc2)
491+ return c, c2
492+ end
493+
494+ """ Update Verlet neighbour list
495+
496+ The cell and list for any given particle is only update if it has moved a distance > dr/2 since the last update"""
497+ function update_neighbour_list! (system:: Particles , i:: Int , neighbour_list:: VerletList )
498+ # Check if particle moved for more than half of dr since last update
499+ position_i = get_position (system, i)
500+ # WARNING: assumes that positions are never wrapped
501+ dx = position_i - neighbour_list. positions_at_last_update[i]
502+ if sum (abs2, dx) < neighbour_list. dr2o4
503+ return nothing
504+ end
505+
506+ # Need to recompute neighbour list for the particle
507+ # Alternatively, we could redo everything and reset all positions, unclear which one is the fastest
508+
509+ # First, remove i for all other lists
510+ @inbounds for j in neighbour_list. neighbours[i]
511+ # TODO : benchmark against filter
512+ deleteat! (neighbour_list. neighbours[j], neighbour_list. neighbours[j] .== i)
513+ end
514+ neighbour_list. neighbours[i] = Int[]
515+
516+ # Update i's cell if needed
517+ c, c2 = old_new_cell (system, i, neighbour_list)
518+ if c != c2
519+ update_cell_list! (i, c, c2, neighbour_list)
520+ end
521+
522+ # Then, recreate list for i
523+ r_cutoff2 = (neighbour_list. rcut + neighbour_list. dr)^ 2
524+ c = get_cell_index (position_i, neighbour_list)
525+ neighbour_cells = neighbour_list. neighbour_cells[c]
526+ for c2 in neighbour_cells
527+ @inbounds for j in neighbour_list. cells[c2]
528+ if i == j
529+ continue
530+ end
531+ position_j = get_position (system, j)
532+ r2 = nearest_image_distance_squared (position_i, position_j, get_box (system))
533+ if r2 < r_cutoff2
534+ push! (neighbour_list. neighbours[i], j)
535+ push! (neighbour_list. neighbours[j], i)
536+ end
537+ end
538+ end
539+
540+ neighbour_list. positions_at_last_update[i] = copy (position_i)
541+
542+ return nothing
543+ end
0 commit comments