Uniquely Identifying Particles With Hashes

In many cases, one can just identify particles by their position in the particle array, e.g. using sim.particles[5]. However, in cases where particles might get reordered in the particle array finding a particle might be difficult. This is why we added a hash attribute to particles.

In REBOUND particles might get rearranged when a tree code is used for the gravity or collision routine, when particles merge, when a particle leaves the simulation box, or when you manually remove or add particles. In general, therefore, the user should not assume that particles stay at the same index or in the same location in memory. The reliable way to access particles is to assign them hashes and to access particles through them.

Note: When you don't assign particles a hash, they automatically get set to 0. The user is responsible for making sure hashes are unique, so if you set up particles without a hash and later set a particle's hash to 0, you don't know which one you'll get back when you access hash 0. See Possible Pitfalls below.

In this example, we show the basic usage of the hash attribute, which is an unsigned integer.


In [1]:
import rebound
sim = rebound.Simulation()
sim.add(m=1., hash=999)
sim.add(a=0.4, hash="mercury")
sim.add(a=1., hash="earth")
sim.add(a=5., hash="jupiter")

We can now not only access the Earth particle with:


In [2]:
sim.particles[2]


Out[2]:
<rebound.Particle object, m=0.0 x=1.0 y=0.0 z=0.0 vx=0.0 vy=1.0 vz=0.0>

but also with


In [3]:
sim.particles["earth"]


Out[3]:
<rebound.Particle object, m=0.0 x=1.0 y=0.0 z=0.0 vx=0.0 vy=1.0 vz=0.0>

We can access particles with negative indices like a list. We can get the last particle with


In [4]:
sim.particles[-1]


Out[4]:
<rebound.Particle object, m=0.0 x=5.0 y=0.0 z=0.0 vx=0.0 vy=0.4472135954999579 vz=0.0>

Details

We can also access particles through their hash directly. However, to differentiate from passing an integer index, we have to first cast the hash to the underlying C datatype. We can do this through the rebound.hash function:


In [5]:
from rebound import hash as h
sim.particles[h(999)]


Out[5]:
<rebound.Particle object, m=1.0 x=0.0 y=0.0 z=0.0 vx=0.0 vy=0.0 vz=0.0>

which corresponds to particles[0] as it should. sim.particles[999] would try to access index 999, which doesn't exist in the simulation, and REBOUND would raise an AttributeError.

When we above set the hash to a string, REBOUND converted this to an unsigned integer using the same rebound.hash function:


In [6]:
h("earth")


Out[6]:
c_uint(1424801690)

In [7]:
sim.particles[2].hash


Out[7]:
c_uint(1424801690)

The hash attribute always returns the appropriate unsigned integer ctypes type. (Depending on your computer architecture, ctypes.c_uint32 can be an alias for another ctypes type).

So we could also access the earth with:


In [8]:
sim.particles[h(1424801690)]


Out[8]:
<rebound.Particle object, m=0.0 x=1.0 y=0.0 z=0.0 vx=0.0 vy=1.0 vz=0.0>

The numeric hashes could be useful in cases where you have a lot of particles you don't want to assign individual names, but you still need to keep track of them individually as they get rearranged:


In [9]:
for i in range(1,100):
    sim.add(m=0., a=i, hash=i)

In [10]:
sim.particles[99].a


Out[10]:
96.0

In [11]:
sim.particles[h(99)].a


Out[11]:
98.99999999999999

Possible Pitfalls The user is responsible for making sure the hashes are unique. If two particles share the same hash, you could get either one when you access them using their hash (in most cases the first hit in the particles array). Two random strings used for hashes have a $\sim 10^{-9}$ chance of clashing. The most common case is setting a hash to 0:


In [12]:
sim = rebound.Simulation()
sim.add(m=1., hash=0)
sim.add(a=1., hash="earth")
sim.add(a=5.)
sim.particles[h(0)]


Out[12]:
<rebound.Particle object, m=0.0 x=5.0 y=0.0 z=0.0 vx=0.0 vy=0.4472135954999579 vz=0.0>

Here we expected to get back the first particle, but instead got the last one. This is because we didn't assign a hash to the last particle and it got automatically set to 0. If we give hashes to all the particles in the simulation, then there's no clash:


In [13]:
sim = rebound.Simulation()
sim.add(m=1., hash=0)
sim.add(a=1., hash="earth")
sim.add(a=5., hash="jupiter")
sim.particles[h(0)]


Out[13]:
<rebound.Particle object, m=1.0 x=0.0 y=0.0 z=0.0 vx=0.0 vy=0.0 vz=0.0>

Due to details of the ctypes library, comparing two ctypes.c_uint32 instances for equality fails:


In [14]:
h(32) == h(32)


Out[14]:
False

You have to compare the value


In [15]:
h(32).value == h(32).value


Out[15]:
True

See the docs for further information: https://docs.python.org/2/library/ctypes.html


In [ ]: