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]:
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]:
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]:
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')
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)
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]:
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]:
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]:
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 [ ]: