Partlist
Collections of particles in EPOCH are represented by the particle_list
object. These objects abstract much of the operation of the linked lists,
including adding and removing particles and sending particles to other
processors. This page details the functions present in
src/housekeeping/partlist.F90
, which details how particle lists are formed.
create_empty_partlist
SUBROUTINE create_empty_partlist(partlist)
TYPE(particle_list), INTENT(INOUT) :: partlist
create_empty_partlist
is a routine which takes a particle_list
object and sets it up so that it points to no particles at all. It should be
used on newly allocated particle_list objects and when a particle_list has
served its purpose. It DOES NOT destroy the particles linked in the list at
the point that it is called. If the user wishes to delete all the particles in a
particle_list then the routine destroy_partlist
should be used
instead.
create_unsafe_partlist
SUBROUTINE create_unsafe_partlist(partlist, a_particle, &
n_elements)
TYPE(particle_list), INTENT(INOUT) :: partlist
TYPE(particle), POINTER :: a_particle
INTEGER(KIND=8), INTENT(IN) :: n_elements
create_unsafe_partlist
is a routine which allows the creation of
a particle_list which represents a subset of another particle list. This subset
is defined as starting at the particle pointed to by a_particle
and extending for n_elements
elements. The new particle_list is
then flagged as “unsafe” because if it is destroyed for any reason then it
will affect other particle lists. Many particle_list functions can only work
on safe particle lists.
create_unsafe_partlist_by_tail
SUBROUTINE create_unsafe_partlist_by_tail(partlist, head, &
tail)
TYPE(particle_list), INTENT(INOUT) :: partlist
TYPE(particle), POINTER :: head, tail
create_unsafe_partlist_by_tail
is almost identical to
create_unsafe_partlist
, but instead of specifying the first
particle and a number of elements, the user specifies the first and last
elements in the subset of the particle list. If the particle objects specified
for head and tail are not in the same partlist or tail actually comes before
head then the routine will fail in an undefined manner.
create_allocated_partlist
SUBROUTINE create_allocated_partlist(partlist, n_elements)
TYPE(particle_list), INTENT(INOUT) :: partlist
INTEGER(KIND=8), INTENT(IN) :: n_elements
create_allocated_partlist
is a helper routine to setup a new
particle_list and create n_elements
new particle objects already
in place in the list.
You should always use this routine when creating large numbers of new particle objects since there is no guarantee that the internal structure of the particle_list objects will not change in the future. This routine will be modified to reflect any changes in the underlying code.
create_filled_partlist
SUBROUTINE create_filled_partlist(partlist, data_in, &
n_elements)
TYPE(particle_list), INTENT(INOUT) :: partlist
REAL(num), DIMENSION(:), INTENT(IN) :: data_in
INTEGER(KIND=8), INTENT(IN) :: n_elements
create_filled_partlist
is a helper routine to setup a new
particle_list and create n_elements
new particle objects already
in place in the list. These new particle objects are then assigned properties
from the array data_in
where the particle properties are contained
in packed form. The particle data is unpacked from the array using the
unpack_particle
routine.
You should always use this routine, if possible, when copying particles out of packed format since there is no guarantee that the internal structure of the particle_list objects will not change in the future. This routine will be modified to reflect any changes in the underlying code.
test_partlist
FUNCTION test_partlist(partlist)
TYPE(particle_list), INTENT(INOUT) :: partlist
test_partlist
is a routine which tests for various possible types
of error within a particle_list object. It has a number of possible return
codes for different errors, using negative values for errors so severe that
the main tests cannot be run, or with a bitmask for errors in the main tests.
The return codes are:
- 0 - No error, particle_list has passed all tests.
- -1 - Either the head or tail of the particle_list object is NULL. This is a serious error and usually means that there is a serious error inside the particle_list routines.
The other error codes are returned as a bitmask and mean the following
- 1 - A particle_list marked as safe has a head element which is linked to a preceding particle object.
- 2 - A particle_list marked as safe has a tail element which is linked to a proceding particle object.
- 4 - The count property of a particle_list does not correspond to the actual number of objects linked between the head and tail objects. This error code on its own usually means that the count property has been modified improperly.
Note that this routine is only intended for debugging and is very slow. It should never be used by the code in normal operation and all routines should be written in such a way that it is impossible for a particle_list object to become corrupted.
destroy_partlist
SUBROUTINE destroy_partlist(partlist)
TYPE(particle_list), INTENT(INOUT) :: partlist
destroy_partlist
is a helper routine to delete all the particles
attached to a particle_list and free up the memory that they use. It also
guarantees to leave the particle_list object itself in a blank state where new
particles can be added to it. It DOES NOT delete the particle_list object
itself, since it does not know whether or not the particle_list is dynamically
allocated. If using dynamically allocated particle_list objects then it is up
to the user to deallocate them AFTER the attached particles are destroyed using
destroy_partlist
.
If a particle_list is deleted without deleting the attached particle objects, either using this routine or explicitly by the user, then the particles will become orphaned and sit around using memory until the code ends. If this happens regularly then the code will quickly crash, usually with a SIG_SEGV error.
copt_partlist
SUBROUTINE copy_partlist(partlist1, partlist2)
TYPE(particle_list), INTENT(INOUT) :: partlist1, partlist2
copy_partlist
is a routine which sets partlist2
to
point to the same linked list of particles as partlist1
. It does
not copy the particles, just sets the head and tail pointers of
partlist1
to point to the same particle objects as
partlist1
.
append_partlist
SUBROUTINE append_partlist(head, tail)
TYPE(particle_list), INTENT(INOUT) :: head, tail
append_partlist
is a routine which takes the particles
attached to the particle_list object tail
and adds them to the
end of the linked list for particle_list head
. The particle_list
tail
is then set to be an empty particle_list.
This routine can only append one safe particle_list to another safe particle_list.
add_particle_to_partlist
SUBROUTINE add_particle_to_partlist(partlist, new_particle)
TYPE(particle_list), INTENT(INOUT) :: partlist
TYPE(particle), POINTER :: new_particle
add_particle_to_partlist
adds a new
particle (new_particle
) to the end of the linked list of
particles in the particle_list object partlist
. It deals with
cases of empty particle_list objects automatically.
If you want to add a new particle to the end of a particle list you should always use this routine.
remove_particle_from_partlist
SUBROUTINE remove_particle_from_partlist(partlist, &
a_particle)
TYPE(particle_list), INTENT(INOUT) :: partlist
TYPE(particle), POINTER :: a_particle
remove_particle_from_partlist
removes the particle object
specified by a_particle
from the particle_list object given by
partlist
. Be very careful that a_particle
is indeed
in the linked list pointed to by partlist
, otherwise it is possible
for the particle_list object which really does contain a_particle
to be left with an invalid pointer as its head or tail element if
a_particle
is either the head or tail element.
Although this routine does work with unsafe particle_list objects, you should be very careful using it in this case as it can break the head or tail element of the primary particle_list which the unsafe particle_list is a subset of. As a general rule, you should only use this routine to remove particles from a simple particle_list which is a singly referenced primary, safe particle_list.
setup_partlists
SUBROUTINE setup_partlists()
setup_partlists
is a routine which is called once when EPOCH
first starts. It sets the variable nvars
which is the number of
REAL(num) values required to contain all the information about a
single particle object needed when a particle is transferred to another
processor. How the information is packed and unpacked from the particle object
into an array of REAL(num) values is controlled in the functions
pack_particle
and unpack_particle
.
If the particle type gains additional properties as the result of preprocessor
directives then there should be a line which increments nvars
by
the correct number when that preprocessor directive is active. For example:
#ifdef PER_PARTICLE_CHARGE_MASS
nvar = nvar+2
#endif
pack_particle and unpack_particle
SUBROUTINE pack_particle(array, a_particle)
SUBROUTINE unpack_particle(array, a_particle)
REAL(num), DIMENSION(:), INTENT(INOUT) :: array
TYPE(particle), POINTER :: a_particle
pack_particle
and unpack_particle
are subroutines
which are used to copy all the information about a particle
necessary for the particle to be transferred to another processor into a
temporary array before sending to another processor. If a new particle property
has been added to the particle then these routines must be modified to allow
the copying of the new data into the array. The parameter array
is
a REAL(num) array of length nvars
and is the array into which the
data either must be packed or from which it must be
unpacked. a_particle
is the particle object which must either have
its data copied into the array or be
populated with data from the array. No restriction is placed on how the
data should be packed into the data array, but obviously
pack_particle
and unpack_particle
must be inverse
operations so that particles packed by one processor can be unpacked correctly
by another processor.
Since it is very unlikely that EPOCH will be run on anything other than a
homogeneous cluster, it is acceptable to use the Fortran TRANSFER
function to pack incompatible data types into the array
array. Just
make sure that nvars
is defined in setup_partlists
to be long enough to contain all the information. More documentation on the
TRANSFER
function (which is rarely used and dangerous!) can be
found
here.
display_particle
SUBROUTINE display_particle(a_particle)
TYPE(particle), POINTER :: a_particle
Displays the key information about a particle given by the parameter
a_particle
. Used by compare_particles
.
compare_particles
FUNCTION compare_particles(particle1, particle2)
TYPE(particle), POINTER :: particle1, particle2
LOGICAL :: compare_particles
Compares all the properties of two particle objects and displays the
information if they don’t match. Used internally by
test_packed_particles
. If the particle object is extended then
this routine should also be modified to test for equivalence of the new
properties.
test_packed_particles
FUNCTION test_packed_particles(partlist, array, &
npart_in_data)
TYPE(particle_list), INTENT(IN) :: partlist
REAL(num), DIMENSION(:), INTENT(IN) :: array
INTEGER(KIND=8), INTENT(IN) :: npart_in_data
LOGICAL :: test_packed_particles
test_packed_particles
is a routine which checks that a packed
array of particles can be successfully unpacked back into particle objects. The
parameters are:
partlist
- The particle_list corresponding to the original unpacked particles.array
- The REAL(num) array containing the packed data.npart_in_data
- The number of particles which were packed intoarray
.
The routine tests that the number of particles in the particle_list match the
number believed to be in the data array, that the length of the data array is
correct and then unpacks each particle in turn from the data array and uses the
compare_particles
function to compare the particles with the
original versions in the particle_list. If any particles fail the comparison
then an error is output to stdout and the function returns a value of
.FALSE.
. The error message includes the processor
rank on which the problem occurs but the routine does not specifically include
any MPI commands, so it is possible to call the routine on a subset of
processors.
partlist_send
SUBROUTINE partlist_send(partlist, dest)
TYPE(particle_list), INTENT(INOUT) :: partlist
INTEGER, INTENT(IN) :: dest
partlist_send
is a routine for sending all the particles in the
particle_list object partlist
to another processor. The
destination processor is identified by its rank which is given by the
dest
parameter. The routine does not destroy the particle_list
object which is given to it.
partlist_send
uses MPI blocking sends, so unless a matching
partlist_recv
has been posted on dest
then the
routine will deadlock. It would be fairly simple to write a non-blocking
version of partlist_send
, but at present no need for such a
routine has been found.
partlist_recv
SUBROUTINE partlist_recv(partlist, src)
TYPE(particle_list), INTENT(INOUT) :: partlist
INTEGER, INTENT(IN) :: src
partlist_recv
is a routine for receiving particles sent by a call
to partlist_send
and loading them into the particle_list object
partlist
. The source processor is identified by its rank which is
given by the src
parameter. The routine destroys the particle_list
which it is given and indeed will leave orphaned particles if it is not given
an empty particle_list to receive the data.
partlist_recv
uses MPI blocking receives, so unless a matchingpartlist_send
has been posted onsrc
then the routine will deadlock. It would be fairly simple to write a non-blocking version ofpartlist_recv
, but at present no need for such a routine has been found.Although it is not possible to directly use
partlist_recv
to add new particles onto an existing particle_list, it is only two lines to do this. First callpartlist_recv
with a temporary particle_list to receive the data and then useappend_partlist
to attach the particles to the end of the already populated list.
partlist_send_recv
SUBROUTINE partlist_send_recv(partlist_send, &
partlist_recv, dest, src)
TYPE(particle_list), INTENT(INOUT) :: partlist_send
TYPE(particle_list), INTENT(INOUT) :: partlist_recv
INTEGER, INTENT(IN) :: dest, src
partlist_sendrecv
is a routine equivalent to
MPI_SENDRECV
in that it allows overlapping sends and receives to
be written in a single line rather than the end user having to split processors
into red/black ordered pairs for communication. It sends the particle data in
partlist_send
to the processor with rank dest
and
receives particle data sent by processor src
and stores it in the
particle_list partlist_recv
. The routine is destructive to both
sending and receiving particle_lists, and can lead to orphaned particles if a
filled particle_list is passed as partlist_recv
. this is the
routine which is used in the particle boundary conditions.
partlist_sendrecv
uses MPI blocking sendrecv commands, so should be used in matching pairs or the routine will deadlock.Although it is not possible to directly use
partlist_sendrecv
to add new particles onto an existing particle_list, it is only two lines to do this. First callpartlist_sendrecv
with a temporary particle_list to receive the data and then useappend_partlist
to attach the particles to the end of the already populated list.