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)

"""
Explanation: Adding Parameters With REBOUNDx
We start by creating a simulation, attaching REBOUNDx, and adding the effects of general relativity:
End of explanation
"""


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

"""
Explanation: 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:
End of explanation
"""


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

"""
Explanation: 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:
End of explanation
"""


speed = 5
gr.params['c'] = speed

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


speed = 10
gr.params['c']

"""
Explanation: If we now update speed, this will not be reflected in our 'c' parameter:
End of explanation
"""


ps[1].params['force'] = gr

"""
Explanation: 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):
End of explanation
"""


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

"""
Explanation: Now if we update gr, the changes will be reflected in the 'force' parameter:
End of explanation
"""


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

"""
Explanation: If the parameter doesn't exist REBOUNDx will raise an exception, which we can catch and handle however we want
End of explanation
"""


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

"""
Explanation: 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:
End of explanation
"""


from reboundx.extras import REBX_C_PARAM_TYPES
REBX_C_PARAM_TYPES

"""
Explanation: 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:
End of explanation
"""


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

"""
Explanation: For example, say we want a double:
End of explanation
"""


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

"""
Explanation: Custom Parameters
You can also add your own more complicated custom types (for example from another library) straightforwardly, 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.
End of explanation
"""


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

"""
Explanation: We also have to register it as a generic POINTER:
End of explanation
"""


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

mysph.dt

"""
Explanation: 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:
End of explanation
"""


