Posidonius is composed by a N-body simulator written in Rust and a Python package. Rust is a great language but its learning curve is a bit steep at first, thus it is fantastic to have a more user-friendly scripting interface to define the kind of simulation that the user wants to execute. The user will only need to learn Rust for advance cases where the integrator or new forces have to be modified/added.
Included in Posidonius there are already several defined cases in the cases/
directory (including examples of planetary and binary systems), it is recommended to inspect them to better understand how the user can create its own scripts. Let's see an example of a brown dwarf host star with a earth-like planet around:
initial_time = 4.5e6*365.25 # time [days] where simulation starts
time_step = 0.08 # days
time_limit = 365.25 * 1.0e2 # days
historic_snapshot_period = 100.*365.25 # days
recovery_snapshot_period = 100.*historic_snapshot_period # days
consider_effects = posidonius.ConsiderEffects({
"tides": True,
"rotational_flattening": True,
"general_relativity": True,
"disk": True,
"wind": True,
"evolution": True,
})
import posidonius
universe = posidonius.Universe(initial_time, time_limit, time_step,
recovery_snapshot_period, historic_snapshot_period,
consider_effects)
star_mass = 0.08 # Solar masses
star_radius_factor = 0.845649342247916
star_radius = star_radius_factor * posidonius.constants.R_SUN
star_radius_of_gyration = 4.47e-01 # Brown dwarf
star_position = posidonius.Axes(0., 0., 0.)
star_velocity = posidonius.Axes(0., 0., 0.)
# Initialization of stellar spin
star_rotation_period = 70.0 # hours
star_angular_frequency = posidonius.constants.TWO_PI/(star_rotation_period/24.) # days^-1
star_spin = posidonius.Axes(0., 0., star_angular_frequency)
star = posidonius.Particle(star_mass, star_radius, star_radius_of_gyration, star_position, star_velocity, star_spin)
CentralBody
and the rest with OrbitingBody
(although not necessarily all of them). We can use Disabled
when we do not want that this effect is taken into account. In this example, we are defining the effect for the host star, so we will use CentralBody
and these two parameters:
star_tides_parameters = {
"dissipation_factor_scale": 1.0,
"dissipation_factor": 2.006*3.845764e4,
"love_number": 0.307,
}
star_tides = posidonius.effects.tides.CentralBody(star_tides_parameters)
#star_tides = posidonius.effects.tides.OrbitingBody(star_tides_parameters)
#star_tides = posidonius.effects.tides.Disabled()
CentralBody
, OrbitingBody
or Disabled
), thus the same rules apply. In this example we use CentralBody
for the host start, and we need to define the "fluid" love number, which is like the tidal one but for rotational flattening effects:
star_fluid_love_number = 0.307
star_rotational_flattening_parameters = {"love_number": star_fluid_love_number }
star_rotational_flattening = posidonius.effects.rotational_flattening.CentralBody(star_rotational_flattening_parameters)
#star_rotational_flattening = posidonius.effects.rotational_flattening.OrbitingBody(star_rotational_flattening_parameters)
#star_rotational_flattening = posidonius.effects.rotational_flattening.Disabled()
CentralBody
, OrbitingBody
or Disabled
), thus the same rules apply. There are three prescriptions available:
CentralBody
is the most massive of the system)CentralBody
is the most massive of the system)CentralBody
), making Posidonius ready for binary stars
star_general_relativity = posidonius.effects.general_relativity.CentralBody("Kidder1995")
#star_general_relativity = posidonius.effects.general_relativity.CentralBody("Anderson1975")
#star_general_relativity = posidonius.effects.general_relativity.CentralBody("Newhall1983")
#star_general_relativity = posidonius.effects.general_relativity.OrbitingBody()
#star_general_relativity = posidonius.effects.general_relativity.Disabled()
#star_wind = posidonius.effects.wind.Interaction({
## Solar wind parametrisation (Bouvier 1997)
#"k_factor": 4.0e-18, # K_wind = 1.6d47 cgs, which is in Msun.AU2.day
#"rotation_saturation": 1.7592918860102842, # 14. * TWO_PI/25.0, in units of the spin of the Sun today
#})
star_wind = posidonius.effects.wind.Disabled()
CentralBody
), so that the rest of bodies can feel the disk force (OrbitingBody
) although some can be disabled too (Disabled
). In this case, we will not enable a disk:
#disk_surface_density_normalization_gcm = 1000. # g.cm^-2
#disk_surface_density_normalization_SI = disk_surface_density_normalization_gcm * 1.0e-3 * 1.0e4 # kg.m^-2
#disk_properties = {
#'inner_edge_distance': 0.01, # AU
#'outer_edge_distance': 100.0, # AU
#'lifetime': 1.0e5 * 365.25e0, # days
#'alpha': 1.0e-2,
#'surface_density_normalization': disk_surface_density_normalization_SI * (1.0/posidonius.constants.M_SUN) * posidonius.constants.AU**2, # Msun.AU^-2
#'mean_molecular_weight': 2.4,
#}
#star_disk = posidonius.effects.disk.CentralBody(disk_properties)
#star_disk = posidonius.effects.disk.OrbitingBody()
star_disk = posidonius.effects.disk.Disabled()
#star_evolution = posidonius.GalletBolmont2017(star_mass) # mass = 0.30 .. 1.40
#star_evolution = posidonius.BolmontMathis2016(star_mass) # mass = 0.40 .. 1.40
#star_evolution = posidonius.Baraffe2015(star_mass) # mass = 0.01 .. 1.40
#star_evolution = posidonius.Leconte2011(star_mass) # mass = 0.01 .. 0.08
#star_evolution = posidonius.Baraffe1998(star_mass) # Sun (mass = 1.0) or M-Dwarf (mass = 0.1)
#star_evolution = posidonius.LeconteChabrier2013(False) # Jupiter without dissipation of dynamical tides
#star_evolution = posidonius.LeconteChabrier2013(True) # Jupiter with dissipation of dynamical tides
star_evolution = posidonius.NonEvolving()
star.set_tides(star_tides)
star.set_rotational_flattening(star_rotational_flattening)
star.set_general_relativity(star_general_relativity)
star.set_wind(star_wind)
star.set_disk(star_disk)
star.set_evolution(star_evolution)
universe.add_particle(star)
planet_mass_factor = 1.0
planet_mass = planet_mass_factor * posidonius.constants.M_EARTH # Solar masses (3.0e-6 solar masses = 1 earth mass)
#--------------------------------------------------------------------------------
# Earth-like => mass-radius relationship from Fortney 2007
planet_radius_factor = posidonius.tools.mass_radius_relation(planet_mass_factor,
planet_mass_type='factor',
planet_percent_rock=0.70)
planet_radius = planet_radius_factor * posidonius.constants.R_EARTH
planet_radius_of_gyration = 5.75e-01 # Earth type planet
#--------------------------------------------------------------------------------
#////////// Specify initial position and velocity for a stable orbit
#////// Keplerian orbital elements, in the `asteroidal' format of Mercury code
a = 0.018; # semi-major axis (in AU)
e = 0.1; # eccentricity
i = 5. * posidonius.constants.DEG2RAD; # inclination (degrees)
p = 0. * posidonius.constants.DEG2RAD; # argument of pericentre (degrees)
n = 0. * posidonius.constants.DEG2RAD; # longitude of the ascending node (degrees)
l = 0. * posidonius.constants.DEG2RAD; # mean anomaly (degrees)
p = (p + n); # Convert to longitude of perihelion !!
q = a * (1.0 - e); # perihelion distance
planet_position, planet_velocity = posidonius.calculate_cartesian_coordinates(
planet_mass, q, e, i, p, n, l,
masses=[star_mass],
positions=[star_position],
velocities=[star_velocity])
#--------------------------------------------------------------------------------
#////// Initialization of planetary spin
planet_obliquity = 11.459156 * posidonius.constants.DEG2RAD # 0.2 rad
planet_rotation_period = 24. # hours
planet_angular_frequency = posidonius.constants.TWO_PI/(planet_rotation_period/24.) # days^-1
planet_keplerian_orbital_elements = posidonius.calculate_keplerian_orbital_elements(
planet_mass, planet_position, planet_velocity,
masses=[star_mass],
positions=[star_position],
velocities=[star_velocity])
planet_inclination = planet_keplerian_orbital_elements[3]
planet_spin = posidonius.calculate_spin(planet_angular_frequency,
planet_inclination, planet_obliquity)
planet = posidonius.Particle(planet_mass, planet_radius, planet_radius_of_gyration, planet_position, planet_velocity, planet_spin)
# Pseudo-synchronization period
planet_keplerian_orbital_elements = posidonius.calculate_keplerian_orbital_elements(
planet_mass, planet_position, planet_velocity,
masses=[star_mass],
positions=[star_position],
velocities=[star_velocity])
planet_semi_major_axis = planet_keplerian_orbital_elements[0]
planet_eccentricity = planet_keplerian_orbital_elements[2]
planet_semi_major_axis = a
planet_eccentricity = e
planet_pseudo_synchronization_period = posidonius.calculate_pseudo_synchronization_period(
planet_semi_major_axis,
planet_eccentricity,
star_mass, planet_mass)
planet_angular_frequency = posidonius.constants.TWO_PI/(planet_pseudo_synchronization_period/24.) # days^-1
CentralBody
and the rest with OrbitingBody
(although not necessarily all of them). We can use Disabled
when we do not want that this effect is taken into account. In this example, we already defined the effect for the host star, and we will enable it for the planet too so we will use OrbitingBody
and these two parameters:
k2pdelta = 2.465278e-3 # Terrestrial planets (no gas)
planet_tides_parameters = {
"dissipation_factor_scale": 1.0,
"dissipation_factor": 2. * posidonius.constants.K2 * k2pdelta/(3. * np.power(planet_radius, 5)),
"love_number": 0.305,
}
#planet_tides = posidonius.effects.tides.CentralBody(planet_tides_parameters)
planet_tides = posidonius.effects.tides.OrbitingBody(planet_tides_parameters)
#planet_tides = posidonius.effects.tides.Disabled()
CentralBody
, OrbitingBody
or Disabled
), thus the same rules apply. In this example we enabled it for the host start (CentralBody
), and we will also enable it for this planet (OrbitingBody
), thus we need to define the "fluid" love number, which is like the tidal one but for rotational flattening effects:
planet_fluid_love_number = 0.307
planet_rotational_flattening_parameters = {"love_number": planet_fluid_love_number}
#planet_rotational_flattening = posidonius.effects.rotational_flattening.CentralBody(planet_rotational_flattening_parameters)
planet_rotational_flattening = posidonius.effects.rotational_flattening.OrbitingBody(planet_rotational_flattening_parameters)
#planet_rotational_flattening = posidonius.effects.rotational_flattening.Disabled()
CentralBody
, OrbitingBody
or Disabled
), thus the same rules apply. There are three prescriptions available and we already selected Kidder1995 when we enabled the effect for the host star (CentralBody
) so now it is only needed to enable it (OrbitingBody
) for the planet:
#planet_general_relativity = posidonius.effects.general_relativity.CentralBody("Kidder1995")
#planet_general_relativity = posidonius.effects.general_relativity.CentralBody("Anderson1975")
#planet_general_relativity = posidonius.effects.general_relativity.CentralBody("Newhall1983")
planet_general_relativity = posidonius.effects.general_relativity.OrbitingBody()
#planet_general_relativity = posidonius.effects.general_relativity.Disabled()
Disabled
):
#planet_wind = posidonius.effects.wind.Interaction({
## Solar wind parametrisation (Bouvier 1997)
#"k_factor": 4.0e-18, # K_wind = 1.6d47 cgs, which is in Msun.AU2.day
#"rotation_saturation": 1.7592918860102842, # 14. * TWO_PI/25.0, in units of the spin of the Sun today
#})
planet_wind = posidonius.effects.wind.Disabled()
CentralBody
), so that the rest of bodies can feel the disk force (OrbitingBody
) although some can be disabled too (Disabled
). In this case, we will not enable a disk:
#disk_surface_density_normalization_gcm = 1000. # g.cm^-2
#disk_surface_density_normalization_SI = disk_surface_density_normalization_gcm * 1.0e-3 * 1.0e4 # kg.m^-2
#disk_properties = {
#'inner_edge_distance': 0.01, # AU
#'outer_edge_distance': 100.0, # AU
#'lifetime': 1.0e5 * 365.25e0, # days
#'alpha': 1.0e-2,
#'surface_density_normalization': disk_surface_density_normalization_SI * (1.0/posidonius.constants.M_SUN) * posidonius.constants.AU**2, # Msun.AU^-2
#'mean_molecular_weight': 2.4,
#}
#planet_disk = posidonius.effects.disk.CentralBody(disk_properties)
#planet_disk = posidonius.effects.disk.OrbitingBody()
planet_disk = posidonius.effects.disk.Disabled()
#planet_evolution = posidonius.GalletBolmont2017(planet_mass) # mass = 0.30 .. 1.40
#planet_evolution = posidonius.BolmontMathis2016(planet_mass) # mass = 0.40 .. 1.40
#planet_evolution = posidonius.Baraffe2015(planet_mass) # mass = 0.01 .. 1.40
#planet_evolution = posidonius.Leconte2011(planet_mass) # mass = 0.01 .. 0.08
#planet_evolution = posidonius.Baraffe1998(planet_mass) # Sun (mass = 1.0) or M-Dwarf (mass = 0.1)
#planet_evolution = posidonius.LeconteChabrier2013(False) # Jupiter without dissipation of dynamical tides
#planet_evolution = posidonius.LeconteChabrier2013(True) # Jupiter with dissipation of dynamical tides
planet_evolution = posidonius.NonEvolving()
planet.set_tides(planet_tides)
planet.set_rotational_flattening(planet_rotational_flattening)
planet.set_general_relativity(planet_general_relativity)
planet.set_wind(planet_wind)
planet.set_disk(planet_disk)
planet.set_evolution(planet_evolution)
universe.add_particle(planet)
DemocraticHeliocentric
, WHDS
, WHDS
). This is a symplectic integrator that assumes a massive body that gravitationally dominates the system, thus it cannot be used when there are multiple stars. It can be used for the example here defined since it consists on one star and one planet.output_filename = "target/custom.json"
whfast_alternative_coordinates="DemocraticHeliocentric"
#whfast_alternative_coordinates="WHDS"
#whfast_alternative_coordinates="Jacobi"
universe.write(output_filename, integrator="WHFast", whfast_alternative_coordinates=whfast_alternative_coordinates)
#universe.write(output_filename, integrator="IAS15")
#universe.write(output_filename, integrator="LeapFrog")
Once the script is saved (e.g., as custom_case.py
), it can be easily run by executing:
mkdir -p target/
python custom_case.py
And it will create the JSON file in target/custom.json. The examples included in Posidonius are slightly more complete, since the output filename can be specified as an argument (it falls out of the scope of this document to explain that part of the python code):
python cases/Bolmont_et_al_2015/case3.py target/case3.json
python cases/Bolmont_et_al_2015/case4.py target/case4.json
python cases/Bolmont_et_al_2015/case7.py target/case7.json
python cases/example.py target/example.json
An important aspect of N-body simulations (and more in general, for any scientific study) is ensuring that your computations can be reproduced and reverified (Rein & Tamayo 2017). Thanks to the Python script and the JSON output, any study can be completely reproduced by indicating what Posidonius version was used and the original script or JSON file.