Adding Parameters With REBOUNDx

We start by creating a simulation, attaching REBOUNDx, and adding the effects of general relativity:


In [1]:
import rebound
import reboundx

sim = rebound.Simulation()
sim.add(m=1.)
sim.add(a=1.)
ps = sim.particles

rebx = reboundx.Extras(sim)
gr = rebx.load_force('gr')
rebx.add_force(gr)

The documentation page https://reboundx.readthedocs.io/en/latest/effects.html lists the various required and optional parameters that need to be set for each effect in REBOUNDx. Adding these parameters to particles, forces and operators is easy. We do it through the params attribute:


In [2]:
ps[1].params['primary'] = 1
gr.params['c'] = 3.e8

We would now sim.integrate as usual. If we want, we can access these values later (e.g., some effects could update these values as the simulation progresses). Here they don't:


In [3]:
sim.integrate(10.)
gr.params['c']


Out[3]:
300000000.0

Details

For simples types (ints and floats), assigning variables to parameters makes a copy of the value. For example:


In [4]:
speed = 5
gr.params['c'] = speed

If we now update speed, this will not be reflected in our 'c' parameter:


In [5]:
speed = 10
gr.params['c']


Out[5]:
5.0

More complicated objects are assigned as pointers. For example, adding REBOUNDx structures like forces works out of the box. As a simple example (with no meaning whatsoever):


In [6]:
ps[1].params['force'] = gr

Now if we update gr, the changes will be reflected in the 'force' parameter:


In [7]:
gr.params['c'] = 10
newgr = ps[1].params['force']
newgr.params['c']


Out[7]:
10.0

If the parameter doesn't exist REBOUNDx will raise an exception, which we can catch and handle however we want


In [8]:
try:
    waterfrac = ps[1].params['waterfrac']
except:
    print('No water on this planet')


No water on this planet

Adding Your Own Parameters

In order to go back and forth between Python and C, REBOUNDx keeps a list of registered parameter names with their corresponding types. This list is compiled from all the parameters used by the various forces and operators in REBOUNDx listed here: https://reboundx.readthedocs.io/en/latest/effects.html.

If you try to add one that's not on the list, it will complain:


In [9]:
try:
    gr.params['q'] = 7
except AttributeError as e:
    print(e)


REBOUNDx Error: Parameter 'q' not found in REBOUNDx. Need to register it first.

You can register the name permanently on the C side, but can also do it from Python. You must pass a name along with one of the C types:


In [10]:
from reboundx.extras import REBX_C_PARAM_TYPES
REBX_C_PARAM_TYPES


Out[10]:
{'REBX_TYPE_NONE': 0,
 'REBX_TYPE_DOUBLE': 1,
 'REBX_TYPE_INT': 2,
 'REBX_TYPE_POINTER': 3,
 'REBX_TYPE_FORCE': 4}

For example, say we want a double:


In [11]:
rebx.register_param("q", "REBX_TYPE_DOUBLE")
gr.params['q'] = 7
gr.params['q']


Out[11]:
7.0

Custom Parameters

You can also add your own more complicated custom types (for example from another library) straightfowardly, with a couple caveats. First, the object must be wrapped as a ctypes object in order to communicate with the REBOUNDx C library, e.g.


In [12]:
from ctypes import *
class SPH_sim(Structure):
    _fields_ = [("dt", c_double),
                ("Nparticles", c_int)]

my_sph_sim = SPH_sim()
my_sph_sim.dt = 0.1
my_sph_sim.Nparticles = 10000

We also have to register it as a generic POINTER:


In [13]:
rebx.register_param("sph", "REBX_TYPE_POINTER")
gr.params['sph'] = my_sph_sim

Now when we get the parameter, REBOUNDx does not know how to cast it. You get a ctypes.c_void_p object back, which you have to manually cast to the Structure class we've created. See the ctypes library documentation for details:


In [14]:
mysph = gr.params['sph']
mysph = cast(mysph, POINTER(SPH_sim)).contents

In [15]:
mysph.dt


Out[15]:
0.1

Caveats for Custom Parameters

Since REBOUNDx does not know about the custom objects created in this way there are two main caveats.

  • The user is responsible for ensuring that the memory for their custom objects remains allocated. For example if the custom parameter is instantiated in a function and assigned as a parameter, but then leaves the function and gets garbage collected, this will obviously lead to undefined behavior! By contrast, REBOUNDx will retain memory for simple ints and floats, as well as any objects that it has created (e.g. forces or operators).

  • Custom parameters will not be written or read from REBOUNDx binaries


In [ ]: