def integrate_system(N, t_end, seed=None): total_mass = N | units.MSun length = 1 | units.parsec converter = nbody_system.nbody_to_si(total_mass, length) gravity = ph4(convert_nbody=converter) gravity.initialize_code() gravity.parameters.set_defaults() gravity.parameters.epsilon_squared = (0.0 | units.parsec)**2 if seed is not None: numpy.random.seed(seed) stars = new_plummer_model(N, convert_nbody=converter) stars.mass = total_mass / N stars.scale_to_standard( convert_nbody=converter, smoothing_length_squared=gravity.parameters.epsilon_squared) id = numpy.arange(N) stars.id = id + 1 stars.radius = 0.5 / N | units.parsec gravity.particles.add_particles(stars) stopping_condition = gravity.stopping_conditions.collision_detection stopping_condition.enable() init_smalln(converter) kep = Kepler(unit_converter=converter) kep.initialize_code() multiples_code = multiples.Multiples(gravity, new_smalln, kep, constants.G) multiples_code.neighbor_perturbation_limit = 0.05 multiples_code.global_debug = 1 ###BOOKLISTSTOP2### # global_debug = 0: no output from multiples # 1: minimal output # 2: debugging output # 3: even more output print('') print('multiples_code.neighbor_veto =', \ multiples_code.neighbor_veto) print('multiples_code.neighbor_perturbation_limit =', \ multiples_code.neighbor_perturbation_limit) print('multiples_code.retain_binary_apocenter =', \ multiples_code.retain_binary_apocenter) print('multiples_code.wide_perturbation_limit =', \ multiples_code.wide_perturbation_limit) time = numpy.sqrt(length**3 / (constants.G * total_mass)) print('\ntime unit =', time.in_(units.Myr)) ###BOOKLISTSTART3### E0 = print_diagnostics(multiples_code) multiples_code.evolve_model(t_end) print_diagnostics(multiples_code, E0) ###BOOKLISTSTOP3### gravity.stop() kep.stop() stop_smalln()
def update_host_star(system, converter=None, kepler_worker=None): if kepler_worker == None: if converter == None: converter = nbody_system.nbody_to_si( system.mass.sum(), 2 * np.max(system.radius.number) | system.radius.unit) kep_p = Kepler(unit_converter=converter, redirection='none') kep_p.initialize_code() else: kep_p = kepler_worker stars = util.get_stars(system) planets = util.get_planets(system) p_NearestStar = planets.nearest_neighbour(stars) for i, planet in enumerate(planets): likely_host = p_NearestStar[i] update_orb_elem(likely_host, [planet], converter=converter, kepler_worker=kep_p) if planet.eccentricity >= 1.0: for s in stars - likely_host: update_orb_elem(s, [planet], converter=converter, kepler_worker=kep_p) if planet.eccentricity < 1.0: planet.host_star = s.id break elif planet.eccentricity >= 1.0: planet.host_star = -1 else: planet.host_star = likely_host.id if kepler_worker == None: kep_p.stop()
def integrate_system(N, t_end, seed=None): gravity = ph4() gravity.initialize_code() gravity.parameters.set_defaults() if seed is not None: numpy.random.seed(seed) stars = new_plummer_model(N) stars.mass = 1./N | nbody_system.mass stars.scale_to_standard(smoothing_length_squared = gravity.parameters.epsilon_squared) id = numpy.arange(N) stars.id = id+1 # Set dynamical radii for encounters. stars.radius = 0.5*stars.mass.number | nbody_system.length gravity.particles.add_particles(stars) stopping_condition = gravity.stopping_conditions.collision_detection stopping_condition.enable() init_smalln() kep = Kepler(unit_converter=None) kep.initialize_code() multiples_code = multiples.Multiples(gravity, new_smalln, kep) multiples_code.neighbor_perturbation_limit = 0.05 multiples_code.global_debug = 1 # global_debug = 0: no output from multiples # 1: minimal output # 2: debugging output # 3: even more output print '' print 'multiples_code.neighbor_veto =', \ multiples_code.neighbor_veto print 'multiples_code.neighbor_perturbation_limit =', \ multiples_code.neighbor_perturbation_limit print 'multiples_code.retain_binary_apocenter =', \ multiples_code.retain_binary_apocenter print 'multiples_code.wide_perturbation_limit =', \ multiples_code.wide_perturbation_limit # Advance the system. E0 = print_diagnostics(multiples_code) multiples_code.evolve_model(t_end) print_diagnostics(multiples_code, E0) gravity.stop() kep.stop() stop_smalln()
def integrate_system(N, t_end, seed=None): gravity = ph4() gravity.initialize_code() gravity.parameters.set_defaults() if seed is not None: numpy.random.seed(seed) stars = new_plummer_model(N) stars.mass = 1./N | nbody_system.mass stars.scale_to_standard(smoothing_length_squared = gravity.parameters.epsilon_squared) id = numpy.arange(N) stars.id = id+1 # Set dynamical radii for encounters. stars.radius = 0.5*stars.mass.number | nbody_system.length gravity.particles.add_particles(stars) stopping_condition = gravity.stopping_conditions.collision_detection stopping_condition.enable() init_smalln() kep = Kepler(unit_converter=None) kep.initialize_code() multiples_code = multiples.Multiples(gravity, new_smalln, kep) multiples_code.neighbor_perturbation_limit = 0.05 multiples_code.global_debug = 1 # global_debug = 0: no output from multiples # 1: minimal output # 2: debugging output # 3: even more output print('') print('multiples_code.neighbor_veto =', \ multiples_code.neighbor_veto) print('multiples_code.neighbor_perturbation_limit =', \ multiples_code.neighbor_perturbation_limit) print('multiples_code.retain_binary_apocenter =', \ multiples_code.retain_binary_apocenter) print('multiples_code.wide_perturbation_limit =', \ multiples_code.wide_perturbation_limit) # Advance the system. E0 = print_diagnostics(multiples_code) multiples_code.evolve_model(t_end) print_diagnostics(multiples_code, E0) gravity.stop() kep.stop() stop_smalln()
def get_galaxies_in_orbit(m_a=10.e11|units.MSun, m_b=10.e11|units.MSun, ecc=0.5, r_min=25.|units.kpc, t_start=None): """ binary galaxy with orbit of given parameters -- if ecc=>1, start at t_start (p625, t_start=-10=-10*100Myr) -- if ecc<1, start at apocenter (p644) """ converter=nbody_system.nbody_to_si(m_a+m_b,1|units.kpc) semi = r_min/(1.-ecc) # relative position and velocity vectors at the pericenter using kepler kepler = Kepler_twobody(converter) kepler.initialize_code() kepler.initialize_from_elements(mass=(m_a+m_b), semi=semi, ecc=ecc, periastron=r_min) # at periastron # evolve back till initial position if ( ecc<1. ): kepler.return_to_apastron() else: kepler.transform_to_time(t_start) # get time of the orbit t_orbit = kepler.get_time() rl = kepler.get_separation_vector() r = [rl[0].value_in(units.AU), rl[1].value_in(units.AU), rl[2].value_in(units.AU)] | units.AU vl = kepler.get_velocity_vector() v = [vl[0].value_in(units.kms), vl[1].value_in(units.kms), vl[2].value_in(units.kms)] | units.kms kepler.stop() # assign particle atributes galaxies = Particles(2) galaxies[0].mass = m_a galaxies[0].position = (0,0,0) | units.AU galaxies[0].velocity = (0,0,0) | units.kms galaxies[1].mass = m_b galaxies[1].position = r galaxies[1].velocity = v # identification galaxies[0].id = 'a0' galaxies[1].id = 'b0' galaxies.move_to_center() return galaxies, t_orbit
def new_system(star_mass=1 | units.MSun, star_radius=1 | units.RSun, disk_minimum_radius=0.05 | units.AU, disk_maximum_radius=10 | units.AU, disk_mass=20 | MEarth, accurancy=0.0001, planet_density=3 | units.g / units.cm**3, rng=None, kepler=None): central_particle = Particle() central_particle.mass = star_mass central_particle.position = (0, 0, 0) | units.AU central_particle.velocity = (0, 0, 0) | units.kms central_particle.radius = star_radius central_particle.name = "star" central_particle.type = "star" central_particle.id = 0 if rng is None: rng = numpy.random converter = nbody_system.nbody_to_si(1 | units.MSun, 1 | units.AU) if kepler is None: kepler = Kepler(converter) kepler.initialize_code() m, r, f = new_planet_distribution(disk_minimum_radius, disk_maximum_radius, disk_mass, accurancy) planets = make_planets(central_particle, m, r, density=planet_density, phi=0, theta=None, kepler=kepler, rng=rng) planets.name = "planet" planets.type = "planet" for i in range(len(planets)): planets[i].id = i central_particle.planets = planets kepler.stop() p = Particles() p.add_particle(central_particle) return p
def get_binary_elements(p): comp1 = p.child1 comp2 = p.child2 kep = Kepler(redirection = "none") kep.initialize_code() mass = comp1.mass + comp2.mass pos = [comp2.x-comp1.x, comp2.y-comp1.y, comp2.z-comp1.z] vel = [comp2.vx-comp1.vx, comp2.vy-comp1.vy, comp2.vz-comp1.vz] kep.initialize_from_dyn(mass, pos[0], pos[1], pos[2], vel[0], vel[1], vel[2]) a,e = kep.get_elements() kep.stop() return mass,a,e
def get_component_binary_elements(comp1, comp2): kep = Kepler(redirection = "none") kep.initialize_code() mass = comp1.mass + comp2.mass pos = comp2.position - comp1.position vel = comp2.velocity - comp1.velocity kep.initialize_from_dyn(mass, pos[0], pos[1], pos[2], vel[0], vel[1], vel[2]) a,e = kep.get_elements() r = kep.get_separation() E,J = kep.get_integrals() # per unit reduced mass, note kep.stop() return mass,a,e,r,E
def get_binary_elements(p): comp1 = p.child1 comp2 = p.child2 kep = Kepler(redirection="none") kep.initialize_code() mass = comp1.mass + comp2.mass pos = [comp2.x - comp1.x, comp2.y - comp1.y, comp2.z - comp1.z] vel = [comp2.vx - comp1.vx, comp2.vy - comp1.vy, comp2.vz - comp1.vz] kep.initialize_from_dyn(mass, pos[0], pos[1], pos[2], vel[0], vel[1], vel[2]) a, e = kep.get_elements() kep.stop() return mass, a, e
def get_component_binary_elements(comp1, comp2): kep = Kepler(redirection="none") kep.initialize_code() mass = comp1.mass + comp2.mass pos = comp2.position - comp1.position vel = comp2.velocity - comp1.velocity kep.initialize_from_dyn(mass, pos[0], pos[1], pos[2], vel[0], vel[1], vel[2]) a, e = kep.get_elements() r = kep.get_separation() E, J = kep.get_integrals() # per unit reduced mass, note kep.stop() return mass, a, e, r, E
def relative_position_and_velocity_from_orbital_elements( mass1, mass2, semimajor_axis, eccentricity, mean_anomaly, seed=None): """ Function that returns relative positions and velocity vectors or orbiters with masses mass2 of the central body with mass mass1 in Cartesian coordinates; for vectors of orbital elements -- semi-major axes, eccentricities, mean anomalies. 3D orientation of orbits (inclination, longitude of ascending node and argument of periapsis) are random. (cos(incl) is uniform -1--1, longitude of ascending node and argument of periapsis are uniform 0--2pi) Assuming mass1 is static in the center [0,0,0] m, [0,0,0] km/s (that is mass2<<mass1) """ position_vectors = [] velocity_vectors = [] converter = nbody_system.nbody_to_si(1 | units.MSun, 1 | units.AU) kepler = Kepler(converter) kepler.initialize_code() r_vec = (0., 0., 0.) | units.AU v_vec = (0., 0., 0.) | units.kms # to change seed for each particle if seed is not None: i = 0 for m2_i, a_i, ecc_i, ma_i in zip(mass2, semimajor_axis, eccentricity, mean_anomaly): #print m2_i, a_i, ecc_i, ma_i if seed is not None: kepler.set_random(seed + i) i = i + 1 kepler.initialize_from_elements(mass=(mass1 + m2_i), semi=a_i, ecc=ecc_i, mean_anomaly=ma_i, random_orientation=-1) ri = kepler.get_separation_vector() vi = kepler.get_velocity_vector() # this is to get ~half of the orbits retrograde (that is with inclination # of 90--180 degrees) --> velocity = -velocity vel_vec_dir = numpy.random.random() if (vel_vec_dir <= 0.5): vel_orientation = 1. else: vel_orientation = -1. position_vectors.append([ri[0], ri[1], ri[2]]) velocity_vectors.append([ vel_orientation * vi[0], vel_orientation * vi[1], vel_orientation * vi[2] ]) kepler.stop() return position_vectors, velocity_vectors
def new_system( star_mass = 1|units.MSun, star_radius = 1|units.RSun, disk_minumum_radius = 0.05 | units.AU, disk_maximum_radius = 10 | units.AU, disk_mass = 20 | MEarth, accurancy = 0.0001, planet_density = 3 | units.g/units.cm**3, rng = None, kepler = None): central_particle = Particle() central_particle.mass = star_mass central_particle.position = (0,0,0) | units.AU central_particle.velocity = (0,0,0) | units.kms central_particle.radius = star_radius if rng is None: rng = numpy.random converter = nbody_system.nbody_to_si(1|units.MSun, 1 | units.AU) if kepler is None: kepler = Kepler(converter) kepler.initialize_code() m, r, f = new_planet_distribution( disk_minumum_radius, disk_maximum_radius, disk_mass, accurancy ) planets = make_planets( central_particle, m, r, density = planet_density, phi = 0, theta = None, kepler = kepler, rng = rng ) central_particle.planets = planets kepler.stop() p = Particles() p.add_particle(central_particle) return p
def relative_position_and_velocity_from_orbital_elements(mass1, mass2, semimajor_axis, eccentricity, mean_anomaly, seed=None): """ Function that returns relative positions and velocity vectors or orbiters with masses mass2 of the central body with mass mass1 in Cartesian coordinates; for vectors of orbital elements -- semi-major axes, eccentricities, mean anomalies. 3D orientation of orbits (inclination, longitude of ascending node and argument of periapsis) are random. (cos(incl) is uniform -1--1, longitude of ascending node and argument of periapsis are uniform 0--2pi) Assuming mass1 is static in the center [0,0,0] m, [0,0,0] km/s (that is mass2<<mass1) """ position_vectors = [] velocity_vectors = [] converter = nbody_system.nbody_to_si(1|units.MSun,1|units.AU) kepler = Kepler(converter) kepler.initialize_code() r_vec = (0.,0.,0.) | units.AU v_vec = (0.,0.,0.) | units.kms # to change seed for each particle if seed is not None: i=0 for m2_i, a_i, ecc_i, ma_i in zip(mass2, semimajor_axis, eccentricity, mean_anomaly): #print m2_i, a_i, ecc_i, ma_i if seed is not None: kepler.set_random(seed+i) i=i+1 kepler.initialize_from_elements(mass=(mass1+m2_i),semi=a_i,ecc=ecc_i,mean_anomaly=ma_i,random_orientation=-1) ri = kepler.get_separation_vector() vi = kepler.get_velocity_vector() # this is to get ~half of the orbits retrograde (that is with inclination # of 90--180 degrees) --> velocity = -velocity vel_vec_dir = numpy.random.random() if (vel_vec_dir<=0.5): vel_orientation = 1. else: vel_orientation = -1. position_vectors.append([ri[0], ri[1], ri[2]]) velocity_vectors.append([vel_orientation*vi[0], vel_orientation*vi[1], vel_orientation*vi[2]]) kepler.stop() return position_vectors, velocity_vectors
def run_kepler(mass, semi, ecc, time): kep = Kepler(redirection='none') kep.initialize_code() kep.set_longitudinal_unit_vector(1.0, 1.0, 0.0) kep.initialize_from_elements(mass, semi, ecc) a,e = kep.get_elements() p = kep.get_periastron() print "elements:", a, e, p kep.transform_to_time(time) x,y,z = kep.get_separation_vector() print "separation:", x,y,z x,y,z = kep.get_longitudinal_unit_vector() print "longitudinal:", x,y,z pos = [1, 0, 0] | nbody_system.length vel = [0, 0.5, 0] | nbody_system.speed kep.initialize_from_dyn(mass, pos[0], pos[1], pos[2], vel[0], vel[1], vel[2]) a,e = kep.get_elements() p = kep.get_periastron() print "elements:", a, e, p kep.transform_to_time(time) x,y,z = kep.get_separation_vector() print "separation:", x,y,z x,y,z = kep.get_velocity_vector() print "velocity:", x,y,z x,y,z = kep.get_longitudinal_unit_vector() print "longitudinal:", x,y,z kep.set_random(42) kep.make_binary_scattering(0.5 | nbody_system.mass, 0.5, 0.5 | nbody_system.mass, 0.0 | nbody_system.speed, 0.0 | nbody_system.length, 1.e-6, 0) kep.stop()
def orbital_parameters_for_the_planets(bodies, verbose=True): from amuse.community.kepler.interface import Kepler kepler = Kepler(redirection = "none") kepler.initialize_code() # kep_converter=nbody_system.nbody_to_si(1|units.MSun, 10|units.AU) converter=nbody_system.nbody_to_si(1.0|units.MSun, 1|units.AU) a = [] | units.AU e = [] m = [] | units.MSun name = [] for bi in bodies[1:]: ai, ei, M, ms = calculate_orbital_elements(bodies[0], bi, kepler, converter) name.append(bi.name) a.append(ai) e.append(ei) m.append(ms) kepler.stop() if verbose: for i in range(len(a)): print("Planet: ", name[i], a[i], e[i], m[i]) return a, e
def update_orb_elem(host_star, planets, converter=None, kepler_worker=None): if kepler_worker == None: if converter == None: tot_sys = Particles(particles=(host_star, planets)) converter = nbody_system.nbody_to_si(tot_sys.mass.sum(), 2 * host_star.radius) kep_p = Kepler(unit_converter=converter, redirection='none') kep_p.initialize_code() else: kep_p = kepler_worker for planet in planets: total_mass = host_star.mass + planet.mass kep_pos = host_star.position - planet.position kep_vel = host_star.velocity - planet.velocity kep_p.initialize_from_dyn(total_mass, kep_pos[0], kep_pos[1], kep_pos[2], kep_vel[0], kep_vel[1], kep_vel[2]) planet.semimajor_axis, planet.eccentricity = kep_p.get_elements() planet.period = kep_p.get_period() planet.true_anomaly, planet.mean_anomaly = kep_p.get_angles() if kepler_worker == None: kep_p.stop()
def GetValues(cluster_name, num_workers=1, use_gpu=1, gpu_ID=0, eps2=0.0 | nbody_system.length**2, delta_t=0.05 | nbody_system.time): # This function uses calls upon the restart fuction to reload the multiples from the simulation to use # the multiples function to get the Energy and it correction, num_files can probably be replaced if we # can measure how many files are in a cluster, maybe glob.glob can do that. # This function returns the arrays below: Energy = [] UncorrectedEnergy = [] Time = [] Kinetic = [] Potential = [] L = [] P = [] i = 0 # You will need to change the File Path varibale to run this on anyone else's account file_path = "/home/draco/jthornton/Tycho/Restart/" file_loc = file_path + cluster_name search = glob.glob(file_loc + "*.hdf5") for key in search: if i % 2 == 0: # This loop will go through the restart files of the cluster and append energy and momentum values to the corresponding arrays # First get the restart naming correct restart_file = key[:-11] # Second retrieve the timestep from the file and convert it to a float time_grab = [] time_grab = key.split("_") time = float(time_grab[2]) | nbody_system.time if use_gpu == 1: gravity = ph4(number_of_workers=num_workers, redirection="none", mode="gpu") else: gravity = grav(number_of_workers=num_workers, redirection="none") # Initializing PH4 with Initial Conditions print "Initializing gravity" gravity.initialize_code() gravity.parameters.set_defaults() gravity.parameters.begin_time = time gravity.parameters.epsilon_squared = eps2 gravity.parameters.timestep_parameter = delta_t.number # Setting up the Code to Run with GPUs Provided by Command Line gravity.parameters.use_gpu = use_gpu gravity.parameters.gpu_id = gpu_ID # Initializing Kepler and SmallN print "Initializing Kepler" kep = Kepler(None, redirection="none") kep.initialize_code() print "Initializing SmallN" util.init_smalln() MasterSet = [] print "Retrieving data" MasterSet, multiples_code = read.read_state_from_file( restart_file, gravity, kep, util.new_smalln()) # Setting Up the Stopping Conditions in PH4 stopping_condition = gravity.stopping_conditions.collision_detection stopping_condition.enable() sys.stdout.flush() # Starting the AMUSE Channel for PH4 grav_channel = gravity.particles.new_channel_to(MasterSet) print "Reload Successful" U = multiples_code.potential_energy T = multiples_code.kinetic_energy Etop = T + U print "T: " print T print "U: " print U angular_momentum = MasterSet.total_angular_momentum() momentum = MasterSet.total_momentum() Nmul, Nbin, Emul = multiples_code.get_total_multiple_energy() tmp1, tmp2, Emul2 = multiples_code.get_total_multiple_energy2() Etot = Etop + Emul Eext = multiples_code.multiples_external_tidal_correction Eint = multiples_code.multiples_internal_tidal_correction Eerr = multiples_code.multiples_integration_energy_error Edel = multiples_code.multiples_external_tidal_correction \ + multiples_code.multiples_internal_tidal_correction \ + multiples_code.multiples_integration_energy_error Ecor = Etot - Edel print "Ecor: " print Ecor gravity.stop() kep.stop() util.stop_smalln() Energy.append(Ecor.number) UncorrectedEnergy.append(Etop.number) Time.append(time.number) Kinetic.append(T.number) Potential.append(U.number) L.append(angular_momentum.number) P.append(momentum.number) i += 1 return Energy, UncorrectedEnergy, Time, Kinetic, Potential, L, P
sun_and_stone = Particles(2) sun_and_stone[0].position = [-5.40085336308e+13, -5.92288255636e+13, 0. ] | units.m sun_and_stone[0].velocity = [-68.4281023588, -90.213640012, 0. ] | (units.m / units.s) sun_and_stone[0].mass = 1.98892e+30 | units.kg sun_and_stone[1].position = [2.31421952844e+17, 1.72742642121e+17, 0. ] | units.m sun_and_stone[1].velocity = [500794.477878, 373808.365427, 0. ] | (units.m / units.s) sun_and_stone[1].mass = 0. | units.kg print " particles:" print sun_and_stone converter = nbody_system.nbody_to_si(1 | units.MSun, 1 | units.AU) kepler = Kepler(converter) kepler.initialize_code() kepler.initialize_from_particles(sun_and_stone) semi, ecc = kepler.get_elements() apo = kepler.get_apastron() per = kepler.get_periastron() period = kepler.get_period() kepler.stop() print " semi-major axis ", semi.in_(units.AU) print " eccentricity ", ecc print " apocenter ", apo.in_(units.AU) print " pericenter ", per.in_(units.AU) print " period", period
class StellarEncounterInHydrodynamics(object): """ Resolves collisions between stars by converting them to SPH models, let them collide in an SPH code, and converting the resulting SPH particle distribution back to a 1D stellar evolution model. Requires a stellar evolution code to supply the internal structure of the stars for the convert_stellar_model_to_SPH routine. Requires a gravity code to set up the initial configuration. The stars in the gravity code have typically already collided, so they are first "evolved" back in time up to a certain separation, assuming Keplerian motion. :argument number_of_particles: Total number of gas particles in the SPH simulation :argument hydrodynamics: SPH code class for the simulation :argument initial_separation: a factor relative to the sum of the radii (1 means in contact, default: 5) """ stellar_evolution_code_required = True gravity_code_required = True def __init__( self, number_of_particles, hydrodynamics, initial_separation = 5, relax_sph_models = True, verbose = False, debug = False, hydrodynamics_arguments = dict(), hydrodynamics_parameters = dict(), star_to_sph_arguments = dict(), sph_to_star_arguments = dict(), ): self.number_of_particles = number_of_particles self.hydrodynamics = hydrodynamics self.initial_separation = initial_separation if not relax_sph_models: self.relax = self.no_relax self.verbose = verbose self.debug = debug self.hydrodynamics_arguments = hydrodynamics_arguments self.hydrodynamics_parameters = hydrodynamics_parameters self.star_to_sph_arguments = star_to_sph_arguments self.sph_to_star_arguments = sph_to_star_arguments self.dynamical_timescales_per_step = 1.0 # encounter_is_over check is performed at this interval self.extra_steps_when_encounter_is_over = 3 self.continue_with_kepler = False def handle_collision(self, primary, secondary, stellar_evolution_code=None, gravity_code=None): particles = self.local_copy_of_particles(primary, secondary) self.collect_required_attributes(particles, gravity_code, stellar_evolution_code) self.backtrack_particles(particles) gas_particles = self.convert_stars(particles, stellar_evolution_code) self.simulate_collision(gas_particles) self.models = [convert_SPH_to_stellar_model(group, **self.sph_to_star_arguments) for group in self.groups_after_encounter] return self.new_particles_with_internal_structure_from_models() def new_particles_with_internal_structure_from_models(self): def get_internal_structure(set, particle=None): return self.models[(set.key == particle.key).nonzero()[0]] result = Particles(len(self.models)) result.add_function_attribute("get_internal_structure", None, get_internal_structure) result.mass = [model.dmass.sum().as_quantity_in(self.mass_unit) for model in self.models] result.radius = [model.radius[-1].as_quantity_in(self.radius_unit) for model in self.models] result.position = (self.original_center_of_mass + self.stars_after_encounter.position).as_quantity_in(self.position_unit) result.velocity = (self.original_center_of_mass_velocity + self.stars_after_encounter.velocity).as_quantity_in(self.velocity_unit) return result def local_copy_of_particles(self, primary, secondary): particles = Particles(0) particles.add_particle(primary) particles.add_particle(secondary) return particles def collect_required_attributes(self, particles, gravity_code, stellar_evolution_code): # Collect the required attributes and copy to the particles in memory required_attributes = set(["mass", "x","y","z", "vx","vy","vz", "radius"]) required_attributes -= set(particles.get_attribute_names_defined_in_store()) for code in [stellar_evolution_code, gravity_code]: attrs_in_code = required_attributes & set(code.particles.get_attribute_names_defined_in_store()) if len(attrs_in_code) > 0: code.particles.copy_values_of_attributes_to(list(attrs_in_code), particles) required_attributes -= attrs_in_code self.mass_unit = particles.mass.unit self.radius_unit = particles.radius.unit self.position_unit = particles.position.unit self.velocity_unit = particles.velocity.unit self.dynamical_timescale = numpy.pi * (particles.radius.sum()**3 / (8 * constants.G * particles.total_mass())).sqrt() def start_kepler(self, mass_unit, length_unit): unit_converter = nbody_system.nbody_to_si(mass_unit, length_unit) self.kepler = Kepler(unit_converter, redirection = "none" if self.debug else "null") self.kepler.initialize_code() def initialize_binary_in_kepler(self, star_a, star_b): self.kepler.initialize_from_dyn( star_a.mass + star_b.mass, star_a.x - star_b.x, star_a.y - star_b.y, star_a.z - star_b.z, star_a.vx-star_b.vx, star_a.vy-star_b.vy, star_a.vz-star_b.vz ) return self.kepler def backtrack_particles(self, particles): self.original_center_of_mass = particles.center_of_mass() self.original_center_of_mass_velocity = particles.center_of_mass_velocity() initial_separation = self.initial_separation * particles.radius.sum() if self.verbose: print "Particles at collision:" print particles print "Backtrack particles to initial separation", initial_separation.as_string_in(units.RSun) self.start_kepler(particles.total_mass(), initial_separation) kepler = self.initialize_binary_in_kepler(particles[0], particles[1]) kepler.return_to_radius(initial_separation) self.begin_time = kepler.get_time() particles[1].position = kepler.get_separation_vector() particles[1].velocity = kepler.get_velocity_vector() kepler.advance_to_periastron() self.begin_time -= kepler.get_time() particles[0].position = [0, 0, 0] | units.m particles[0].velocity = [0, 0, 0] | units.m / units.s particles.move_to_center() if self.verbose: print "Backtracking particles done. Initial conditions:" print particles def convert_stars(self, particles, stellar_evolution_code): n_particles = self.divide_number_of_particles(particles) se_colliders = particles.get_intersecting_subset_in(stellar_evolution_code.particles) if self.verbose: print "Converting stars of {0} to SPH models of {1} particles, respectively.".format(particles.mass, n_particles) sph_models = ( self.relax(convert_stellar_model_to_SPH(se_colliders[0], n_particles[0], **self.star_to_sph_arguments)), self.relax(convert_stellar_model_to_SPH(se_colliders[1], n_particles[1], **self.star_to_sph_arguments)) ) gas_particles = Particles() for particle, sph_model in zip(particles, sph_models): sph_model.position += particle.position sph_model.velocity += particle.velocity gas_particles.add_particles(sph_model) if self.verbose: print "Converting stars to SPH particles done" if self.debug: print gas_particles return gas_particles def divide_number_of_particles(self, particles): n1 = int(0.5 + self.number_of_particles * particles[0].mass / particles.total_mass()) return (n1, self.number_of_particles - n1) def relax(self, sph_model): if self.debug: monitor = dict(time=[]|units.day, kinetic=[]|units.J, potential=[]|units.J, thermal=[]|units.J) gas_particles = sph_model.gas_particles hydro = self.new_hydrodynamics(gas_particles) hydro.parameters.artificial_viscosity_alpha = 0.0 # Viscous damping doesn't seem to be very important, but turned off just in case... channel_from_hydro = hydro.gas_particles.new_channel_to(gas_particles) channel_to_hydro = gas_particles.new_channel_to(hydro.gas_particles) dynamical_timescale = numpy.pi * (gas_particles.total_radius()**3 / (8 * constants.G * gas_particles.total_mass())).sqrt() t_end_in_t_dyn = 2.5 # Relax for this many dynamical timescales n_steps = 100 velocity_damp_factor = 1.0 - (2.0*numpy.pi*t_end_in_t_dyn)/n_steps # Critical damping if self.verbose: print "Relaxing SPH model with {0} for {1} ({2} dynamical timescales).".format( self.hydrodynamics.__name__, (t_end_in_t_dyn*dynamical_timescale).as_string_in(units.day), t_end_in_t_dyn) for i_step, time in enumerate(t_end_in_t_dyn*dynamical_timescale * numpy.linspace(1.0/n_steps, 1.0, n_steps)): hydro.evolve_model(time) channel_from_hydro.copy_attributes(["mass","x","y","z","vx","vy","vz","u"]) gas_particles.position -= gas_particles.center_of_mass() gas_particles.velocity = velocity_damp_factor * (gas_particles.velocity - gas_particles.center_of_mass_velocity()) channel_to_hydro.copy_attributes(["x","y","z","vx","vy","vz"]) if self.debug: K, U, Q = hydro.kinetic_energy, hydro.potential_energy, hydro.thermal_energy print "t, K, U, Q:", time, K, U, Q monitor["time"].append(time) monitor["kinetic"].append(K) monitor["potential"].append(U) monitor["thermal"].append(Q) hydro.stop() if self.debug: energy_evolution_plot(monitor["time"], monitor["kinetic"], monitor["potential"], monitor["thermal"]) return gas_particles def no_relax(self, sph_model): return sph_model.gas_particles def new_hop(self, particles): converter = nbody_system.nbody_to_si(particles.total_mass(), 1.0 | units.RSun) if self.debug: print "Output of Hop is redirected to hop_out.log" options = dict(redirection="file", redirect_file="hop_out.log") else: options = dict() hop = Hop(unit_converter=converter, **options) hop.parameters.number_of_neighbors_for_hop = 100 hop.parameters.saddle_density_threshold_factor = 0.8 hop.parameters.relative_saddle_density_threshold = True return hop def new_hydrodynamics(self, gas_particles): unit_converter = nbody_system.nbody_to_si(gas_particles.total_mass(), self.dynamical_timescale) hydro = self.hydrodynamics(unit_converter, **self.hydrodynamics_arguments) hydro.initialize_code() for par, value in self.hydrodynamics_parameters.iteritems(): setattr(hydro.parameters, par, value) hydro.commit_parameters() hydro.gas_particles.add_particles(gas_particles) hydro.commit_particles() return hydro def simulate_collision(self, gas_particles): self.hop = self.new_hop(gas_particles) hydro = self.new_hydrodynamics(gas_particles) channel = hydro.gas_particles.new_channel_to(gas_particles) if self.verbose: print "Simulating collision with {0} from {1} to {2}.".format( self.hydrodynamics.__name__, self.begin_time.as_string_in(units.day), (self.dynamical_timescales_per_step * self.dynamical_timescale).as_string_in(units.day)) hydro.evolve_model(self.dynamical_timescales_per_step * self.dynamical_timescale - self.begin_time) channel.copy_attributes(["x","y","z","vx","vy","vz","pressure","density","u"]) extra_steps_counter = 0 while True: if self.encounter_is_over(gas_particles): extra_steps_counter += 1 if extra_steps_counter > self.extra_steps_when_encounter_is_over: print "Encounter is over and finished extra steps." break else: print "Encounter is over. Now performing step {0} out of {1} extra steps".format( extra_steps_counter, self.extra_steps_when_encounter_is_over) else: extra_steps_counter = 0 print "Continuing to {0}.".format((hydro.model_time + self.next_dt + self.begin_time).as_string_in(units.day)) if self.continue_with_kepler: self.evolve_with_kepler(hydro) hydro.evolve_model(hydro.model_time + self.next_dt) channel.copy_attributes(["x","y","z","vx","vy","vz","pressure","density","u"]) hydro.stop() self.hop.stop() self.kepler.stop() def encounter_is_over(self, gas_particles): self.next_dt = self.dynamical_timescales_per_step * self.dynamical_timescale groups = self.group_bound_particles(gas_particles) stars = self.convert_groups_to_stars(groups) self.groups_after_encounter = groups self.stars_after_encounter = stars if len(stars) > 1: # Should do full check for stable binaries, triple, multiples, two escapers, # escaping star + binary, etc. # For now we only check whether the two most massive groups will (re)collide a, b = stars.sorted_by_attribute("mass")[-2:] if self.debug: print "System consists of {0} groups. The two most massive are: {1} and {2}.".format(len(stars), a.mass.as_string_in(units.MSun), b.mass.as_string_in(units.MSun)) if self.binary_will_collide(a, b): return False if self.verbose: print "Encounter is over, {0} stars after encounter.".format(len(groups)) return True def group_bound_particles(self, gas_particles): groups, lost = self.analyze_particle_distribution(gas_particles) while len(lost) > 0: if self.debug: group_plot(groups, lost) previous_number_of_lost_particles = len(lost) groups, lost = self.select_bound_particles(groups, lost) if len(lost) == previous_number_of_lost_particles: break return groups def convert_groups_to_stars(self, groups): stars = Particles(len(groups)) for star, group in zip(stars, groups): star.mass = group.total_mass() star.position = group.center_of_mass() star.velocity = group.center_of_mass_velocity() star.radius = group.LagrangianRadii(mf=[0.9], cm=star.position)[0][0] return stars def analyze_particle_distribution(self, gas_particles): if self.verbose: print "Analyzing particle distribution using Hop" if "density" in gas_particles.get_attribute_names_defined_in_store(): if self.debug: print "Using the original particles' density" self.hop.parameters.outer_density_threshold = 0.5 * gas_particles.density.mean() self.hop.particles.add_particles(gas_particles) gas_particles.copy_values_of_attribute_to("density", self.hop.particles) else: if self.debug: print "Using Hop to calculate the density" self.hop.particles.add_particles(gas_particles) self.hop.calculate_densities() self.hop.parameters.outer_density_threshold = 0.5 * self.hop.particles.density.mean() self.hop.do_hop() result = [] for group in self.hop.groups(): result.append(group.get_intersecting_subset_in(gas_particles)) lost = self.hop.no_group().get_intersecting_subset_in(gas_particles) self.hop.particles.remove_particles(self.hop.particles) return result, lost def select_bound_particles(self, groups, lost): specific_total_energy_relative_to_group = [] | (units.m / units.s)**2 for group in groups: group_mass = group.total_mass() group_com = group.center_of_mass() group_com_velocity = group.center_of_mass_velocity() specific_total_energy_relative_to_group.append( (lost.velocity - group_com_velocity).lengths_squared() + lost.u - constants.G * group_mass / (lost.position - group_com).lengths()) index_minimum = specific_total_energy_relative_to_group.argmin(axis=0) bound=lost[:0] for i, group in enumerate(groups): bound_to_group = lost[numpy.logical_and( index_minimum == i, specific_total_energy_relative_to_group[i] < 0 | (units.m / units.s)**2 )] bound += bound_to_group groups[i] = group + bound_to_group return groups, lost - bound def binary_will_collide(self, a, b): self.continue_with_kepler = False if self.verbose: print "Using Kepler to check whether the two stars will (re)collide." kepler = self.initialize_binary_in_kepler(a, b) true_anomaly = kepler.get_angles()[1] eccentricity = kepler.get_elements()[1] if true_anomaly > 0.0 and eccentricity >= 1.0: if self.verbose: print "Stars are on hyperbolic/parabolic orbits and moving away from each other, interaction is over." return False periastron = kepler.get_periastron() will_collide = periastron < a.radius + b.radius if self.verbose: print "Stars {0} collide. Distance at periastron: {1}, sum of radii: {2}".format( "will" if will_collide else "won't", periastron.as_string_in(units.RSun), (a.radius + b.radius).as_string_in(units.RSun)) if will_collide: # 1) check whether the stars are still relaxing: less than ~3 t_dyn passed since last moment of contact --> relax # 2) check whether the stars are already within 'initial_separation', else skip (dtmax?) kepler.advance_to_periastron() self.next_dt = kepler.get_time() + self.dynamical_timescales_per_step * self.dynamical_timescale if self.debug: print "Time to collision: {0}, next_dt: {1}".format( kepler.get_time().as_string_in(units.day), self.next_dt.as_string_in(units.day)) if kepler.get_time() > 3 * self.dynamical_timescale and kepler.get_apastron() > 2.0 * self.initial_separation * (a.radius + b.radius): # evolve for 3 * self.dynamical_timescale and skip the rest until ~initial_separation kepler.return_to_apastron() kepler.return_to_radius(a.radius + b.radius) if -kepler.get_time() > 2.9 * self.dynamical_timescale: # If ~3 t_dyn have passed since the end of the collision if self.verbose: print "~3 t_dyn have passed since the end of the collision -> skip to next collision" self.continue_with_kepler = True kepler.advance_to_apastron() kepler.advance_to_radius(2.0 * self.initial_separation * (a.radius + b.radius)) self.skip_to_relative_position_velocity = (kepler.get_separation_vector(), kepler.get_velocity_vector()) self.begin_time = kepler.get_time() kepler.advance_to_periastron() self.next_dt = self.dynamical_timescales_per_step * self.dynamical_timescale + kepler.get_time() - self.begin_time else: self.next_dt = 3 * self.dynamical_timescale + kepler.get_time() return will_collide def evolve_with_kepler(self, hydro): if self.verbose: print "evolve_with_kepler" indices_two_most_massive = self.stars_after_encounter.mass.argsort()[-2:] groups = [self.groups_after_encounter[i] for i in indices_two_most_massive] old_particles = self.stars_after_encounter[indices_two_most_massive] new_particles = Particles(2) new_particles.mass = old_particles.mass new_particles[0].position, new_particles[0].velocity = self.skip_to_relative_position_velocity new_particles.move_to_center() for group, old_particle, new_particle in zip(groups, old_particles, new_particles): in_hydro = group.get_intersecting_subset_in(hydro.gas_particles) if self.verbose: print in_hydro.center_of_mass().as_quantity_in(units.RSun), old_particle.position.as_quantity_in(units.RSun), new_particle.position.as_quantity_in(units.RSun) in_hydro.position += new_particle.position - old_particle.position in_hydro.velocity += new_particle.velocity - old_particle.velocity
multiples_code.evolve_model(time) gravity.synchronize_model() # Copy values from the module to the set in memory. grav_channel.copy() # Copy the index (ID) as used in the module to the id field in # memory. The index is not copied by default, as different # codes may have different indices for the same particle and # we don't want to overwrite silently. grav_channel.copy_attribute("index_in_code", "id") write.write_state_to_file(time, MasterSet, gravity, multiples_code, write_file) gravity.stop() kep.stop() util.stop_smalln() # Setting PH4 as the Top-Level Gravity Code if use_gpu == 1: gravity = ph4(number_of_workers=num_workers, redirection="none", mode="gpu") else: gravity = grav(number_of_workers=num_workers, redirection="none") # Initializing PH4 with Initial Conditions gravity.initialize_code() gravity.parameters.set_defaults() gravity.parameters.begin_time = time gravity.parameters.epsilon_squared = eps2
def integrate_system(N, t_end, seed=None): # Initialize an N-body module and load a stellar system. mass = N|units.MSun length = 1|units.parsec converter = nbody_system.nbody_to_si(mass, length) gravity = ph4(convert_nbody=converter) gravity.initialize_code() gravity.parameters.set_defaults() gravity.parameters.epsilon_squared = (0.0|units.parsec)**2 if seed is not None: numpy.random.seed(seed) stars = new_plummer_model(N, convert_nbody=converter) stars.mass = mass/N stars.scale_to_standard(convert_nbody=converter, smoothing_length_squared = gravity.parameters.epsilon_squared) # Star IDs are important, as they are used in multiples bookkeeping. id = numpy.arange(N) stars.id = id+1 # Set dynamical radii. #stars.radius = 0.5/N | units.parsec stars.radius = 2000 | units.AU gravity.particles.add_particles(stars) # Add Planets do_planets = True if do_planets: systems_SI = create.planetary_systems(stars, N-1, 'test_planets', Jupiter=True) gravity.particles.add_particles(systems_SI) # Enable collision detection. stopping_condition = gravity.stopping_conditions.collision_detection stopping_condition.enable() SmallScaleConverter = nbody_system.nbody_to_si(2*mass/N, 4000 | units.AU) # Define the small-N code. init_smalln(SmallScaleConverter) # Define a Kepler module. kep = Kepler(unit_converter=SmallScaleConverter) kep.initialize_code() # Create the multiples module. global multiples_code multiples_code = multiples2.Multiples(gravity, new_smalln, kep, constants.G) multiples_code.neighbor_perturbation_limit = 0.05 multiples_code.neighbor_veto = True multiples_code.global_debug = 1 # 0: no output from multiples # 1: minimal output # 2: debugging output # 3: even more output # Setting Up the Encounter Handler encounters = EncounterHandler() multiples_code.callback = encounters.handle_encounter_v3 # Print selected multiples settings. print '' print 'multiples_code.neighbor_veto =', \ multiples_code.neighbor_veto print 'multiples_code.neighbor_perturbation_limit =', \ multiples_code.neighbor_perturbation_limit print 'multiples_code.retain_binary_apocenter =', \ multiples_code.retain_binary_apocenter print 'multiples_code.wide_perturbation_limit =', \ multiples_code.wide_perturbation_limit # Advance the system. time = numpy.sqrt(length**3/(constants.G*mass)) print '\ntime unit =', time.in_(units.Myr) E0 = print_diagnostics(multiples_code) dt = 0.1 | units.Myr t_current = 0.0 | units.Myr #gravity.particles[-1].mass += 1 | units.MSun while t_current <= t_end: t_current += dt multiples_code.evolve_model(t_current) print gravity.particles.index_in_code #, gravity.particles[0].x #print multiples_code.particles[-1].key, multiples_code.particles[-1].x print multiples_code._inmemory_particles.id, #multiples_code._inmemory_particles[-1].x #print stars[0].id # Testing the Multiples & Gravity Particle Set Differences #test_psystem_id = systems_SI[0].host_star #test_psystem_hs_grav = [star.x for star in gravity.particles if star.index_in_code == test_psystem_id] #test_psystem_hs_mult = [star.x for star in multiples_code.particles if star.idex_in_code == test_psystem_id] #print "-----------------------------" #print "X Position in PH4", test_pystem_hs_grav.in_(units.parsec) #print "X Position in Multiples", test_psystem_hs_mult.in_(units.parsec) #print "Difference in Particle Sets", test_pystem_hs_grav.in_(units.parsec) - test_psystem_hs_mult.in_(units.parsec) #print "-----------------------------" print_diagnostics(multiples_code, E0) gravity.stop() kep.stop() stop_smalln()
def compress_binary_components(comp1, comp2, scale): # Compress the two-body system consisting of comp1 and comp2 to # lie within distance scale of one another. pos1 = comp1.position pos2 = comp2.position sep12 = ((pos2-pos1)**2).sum() if sep12 > scale*scale: print '\ncompressing components', int(comp1.id.number), \ 'and', int(comp2.id.number), 'to separation', scale.number sys.stdout.flush() mass1 = comp1.mass mass2 = comp2.mass total_mass = mass1 + mass2 vel1 = comp1.velocity vel2 = comp2.velocity cmpos = (mass1*pos1+mass2*pos2)/total_mass cmvel = (mass1*vel1+mass2*vel2)/total_mass # For now, create and delete a temporary kepler # process to handle the transformation. Obviously # more efficient to define a single kepler at the # start of the calculation and reuse it. kep = Kepler(redirection = "none") kep.initialize_code() mass = comp1.mass + comp2.mass rel_pos = pos2 - pos1 rel_vel = vel2 - vel1 kep.initialize_from_dyn(mass, rel_pos[0], rel_pos[1], rel_pos[2], rel_vel[0], rel_vel[1], rel_vel[2]) M,th = kep.get_angles() a,e = kep.get_elements() if e < 1: peri = a*(1-e) apo = a*(1+e) else: peri = a*(e-1) apo = 2*a # OK - used ony to reset scale limit = peri + 0.01*(apo-peri) if scale < limit: scale = limit if M < 0: # print 'approaching' kep.advance_to_periastron() kep.advance_to_radius(limit) else: # print 'receding' if kep.get_separation() < scale: kep.advance_to_radius(limit) else: kep.return_to_radius(scale) # a,e = kep.get_elements() # r = kep.get_separation() # E,J = kep.get_integrals() # print 'kepler: a,e,r =', a.number, e.number, r.number # print 'E, J =', E, J # Note: if periastron > scale, we are now just past periastron. new_rel_pos = kep.get_separation_vector() new_rel_vel = kep.get_velocity_vector() kep.stop() # Enew = 0 # r2 = 0 # for k in range(3): # Enew += 0.5*(new_rel_vel[k].number)**2 # r2 += (new_rel_pos[k].number)**2 # rnew = math.sqrt(r2) # Enew -= mass.number/r1 # print 'E, Enew, rnew =', E.number, E1, r1 # Problem: the vectors returned by kepler are lists, # not numpy arrays, and it looks as though we can say # comp1.position = pos, but not comp1.position[k] = # xxx, as we'd like... Also, we don't know how to # copy a numpy array with units... TODO newpos1 = pos1 - pos1 # stupid trick to create zero vectors newpos2 = pos2 - pos2 # with the proper form and units... newvel1 = vel1 - vel1 newvel2 = vel2 - vel2 frac2 = mass2/total_mass for k in range(3): dxk = new_rel_pos[k] dvk = new_rel_vel[k] newpos1[k] = cmpos[k] - frac2*dxk newpos2[k] = cmpos[k] + (1-frac2)*dxk newvel1[k] = cmvel[k] - frac2*dvk newvel2[k] = cmvel[k] + (1-frac2)*dvk # Perform the changes to comp1 and comp2, and recursively # transmit them to the (currently absolute) coordinates of # all lower components. offset_particle_tree(comp1, newpos1-pos1, newvel1-vel1) offset_particle_tree(comp2, newpos2-pos2, newvel2-vel2)
def run_ph4(infile = None, outfile = None, number_of_stars = 100, number_of_binaries = 0, end_time = 10 | nbody_system.time, delta_t = 1 | nbody_system.time, n_workers = 1, use_gpu = 1, gpu_worker = 1, salpeter = 0, accuracy_parameter = 0.1, softening_length = 0.0 | nbody_system.length, manage_encounters = 1, random_seed = 1234): if random_seed <= 0: numpy.random.seed() random_seed = numpy.random.randint(1, pow(2,31)-1) numpy.random.seed(random_seed) print "random seed =", random_seed if infile != None: print "input file =", infile print "end_time =", end_time.number print "delta_t =", delta_t.number print "n_workers =", n_workers print "use_gpu =", use_gpu print "manage_encounters =", manage_encounters print "\ninitializing the gravity module" sys.stdout.flush() init_smalln() # Note that there are actually three GPU options: # # 1. use the GPU code and allow GPU use (default) # 2. use the GPU code but disable GPU use (-g) # 3. use the non-GPU code (-G) if gpu_worker == 1: try: gravity = grav(number_of_workers = n_workers, redirection = "none", mode = "gpu") except Exception as ex: gravity = grav(number_of_workers = n_workers, redirection = "none") else: gravity = grav(number_of_workers = n_workers, redirection = "none") gravity.initialize_code() gravity.parameters.set_defaults() #----------------------------------------------------------------- if infile == None: print "making a Plummer model" stars = new_plummer_model(number_of_stars) id = numpy.arange(number_of_stars) stars.id = id+1 print "setting particle masses and radii" if salpeter == 0: print 'equal masses' total_mass = 1.0 | nbody_system.mass scaled_mass = total_mass / number_of_stars else: print 'salpeter mass function' scaled_mass = new_salpeter_mass_distribution_nbody(number_of_stars) stars.mass = scaled_mass print "centering stars" stars.move_to_center() print "scaling stars to virial equilibrium" stars.scale_to_standard(smoothing_length_squared = gravity.parameters.epsilon_squared) else: # Read the input data. Units are dynamical (sorry). # Format: id mass pos[3] vel[3] print "reading file", infile id = [] mass = [] pos = [] vel = [] f = open(infile, 'r') count = 0 for line in f: if len(line) > 0: count += 1 cols = line.split() if count == 1: snap = int(cols[0]) elif count == 2: number_of_stars = int(cols[0]) elif count == 3: time = float(cols[0]) | nbody_system.time else: if len(cols) >= 8: id.append(int(cols[0])) mass.append(float(cols[1])) pos.append((float(cols[2]), float(cols[3]), float(cols[4]))) vel.append((float(cols[5]), float(cols[6]), float(cols[7]))) f.close() stars = datamodel.Particles(number_of_stars) stars.id = id stars.mass = mass | nbody_system.mass stars.position = pos | nbody_system.length stars.velocity = vel | nbody_system.speed #stars.radius = 0. | nbody_system.length total_mass = stars.mass.sum() ke = pa.kinetic_energy(stars) kT = ke/(1.5*number_of_stars) if number_of_binaries > 0: # Turn selected stars into binary components. # Only tested for equal-mass case. kep = Kepler(redirection = "none") kep.initialize_code() added_mass = 0.0 | nbody_system.mass # Work with energies rather than semimajor axes. Emin = 10*kT Emax = 20*kT ecc = 0.1 id_count = number_of_stars nbin = 0 for i in range(0, number_of_stars, number_of_stars/number_of_binaries): # Star i is CM, becomes component, add other star at end. nbin += 1 mass = stars[i].mass new_mass = numpy.random.uniform()*mass # uniform q? mbin = mass + new_mass fac = new_mass/mbin E = Emin + numpy.random.uniform()*(Emax-Emin) a = 0.5*nbody_system.G*mass*new_mass/E kep.initialize_from_elements(mbin, a, ecc) dr = quantities.AdaptingVectorQuantity() dr.extend(kep.get_separation_vector()) dv = quantities.AdaptingVectorQuantity() dv.extend(kep.get_velocity_vector()) newstar = datamodel.Particles(1) newstar.mass = new_mass newstar.position = stars[i].position + (1-fac)*dr newstar.velocity = stars[i].velocity + (1-fac)*dv # stars[i].mass = mass stars[i].position = stars[i].position - fac*dr stars[i].velocity = stars[i].velocity - fac*dv id_count += 1 newstar.id = id_count stars.add_particles(newstar) added_mass += new_mass if nbin >= number_of_binaries: break kep.stop() print 'created', nbin, 'binaries' sys.stdout.flush() stars.mass = stars.mass * total_mass/(total_mass+added_mass) number_of_stars += nbin # Set dynamical radii (assuming virial equilibrium and standard # units). Note that this choice should be refined, and updated # as the system evolves. Probably the choice of radius should be # made entirely in the multiples module. TODO. In these units, # M = 1 and <v^2> = 0.5, so the mean 90-degree turnaround impact # parameter is # # b_90 = G (m_1+m_2) / vrel^2 # = 2 <m> / 2<v^2> # = 2 / N for equal masses # # Taking r_i = m_i / 2<v^2> = m_i in virial equilibrium means # that, approximately, "contact" means a 90-degree deflection (r_1 # + r_2 = b_90). A more conservative choice with r_i less than # this value will isolates encounters better, but also place more # load on the large-N dynamical module. stars.radius = stars.mass.number | nbody_system.length time = 0.0 | nbody_system.time # print "IDs:", stars.id.number print "recentering stars" stars.move_to_center() sys.stdout.flush() #----------------------------------------------------------------- if softening_length < 0.0 | nbody_system.length: # Use ~interparticle spacing. Assuming standard units here. TODO eps2 = 0.25*(float(number_of_stars))**(-0.666667) \ | nbody_system.length**2 else: eps2 = softening_length*softening_length print 'softening length =', eps2.sqrt() gravity.parameters.timestep_parameter = accuracy_parameter gravity.parameters.epsilon_squared = eps2 gravity.parameters.use_gpu = use_gpu # gravity.parameters.manage_encounters = manage_encounters print '' print "adding particles" # print stars sys.stdout.flush() gravity.particles.add_particles(stars) gravity.commit_particles() print '' print "number_of_stars =", number_of_stars print "evolving to time =", end_time.number, \ "in steps of", delta_t.number sys.stdout.flush() # Channel to copy values from the code to the set in memory. channel = gravity.particles.new_channel_to(stars) stopping_condition = gravity.stopping_conditions.collision_detection stopping_condition.enable() # Debugging: prevent the multiples code from being called. if 0: stopping_condition.disable() print 'stopping condition disabled' sys.stdout.flush() # ----------------------------------------------------------------- # Create the coupled code and integrate the system to the desired # time, managing interactions internally. kep = init_kepler(stars[0], stars[1]) multiples_code = multiples.Multiples(gravity, new_smalln, kep) multiples_code.neighbor_perturbation_limit = 0.1 #multiples_code.neighbor_distance_factor = 1.0 #multiples_code.neighbor_veto = False #multiples_code.neighbor_distance_factor = 2.0 multiples_code.neighbor_veto = True print '' print 'multiples_code.initial_scale_factor =', \ multiples_code.initial_scale_factor print 'multiples_code.neighbor_perturbation_limit =', \ multiples_code.neighbor_perturbation_limit print 'multiples_code.neighbor_veto =', \ multiples_code.neighbor_veto print 'multiples_code.final_scale_factor =', \ multiples_code.final_scale_factor print 'multiples_code.initial_scatter_factor =', \ multiples_code.initial_scatter_factor print 'multiples_code.final_scatter_factor =', \ multiples_code.final_scatter_factor print 'multiples_code.retain_binary_apocenter =', \ multiples_code.retain_binary_apocenter print 'multiples_code.wide_perturbation_limit =', \ multiples_code.wide_perturbation_limit pre = "%%% " E0,cpu0 = print_log(pre, time, multiples_code) while time < end_time: time += delta_t multiples_code.evolve_model(time) # Copy values from the module to the set in memory. channel.copy() # Copy the index (ID) as used in the module to the id field in # memory. The index is not copied by default, as different # codes may have different indices for the same particle and # we don't want to overwrite silently. channel.copy_attribute("index_in_code", "id") print_log(pre, time, multiples_code, E0, cpu0) sys.stdout.flush() #----------------------------------------------------------------- if not outfile == None: # Write data to a file. f = open(outfile, 'w') #-------------------------------------------------- # Need to save top-level stellar data and parameters. # Need to save multiple data and parameters. f.write('%.15g\n'%(time.number)) for s in multiples_code.stars: write_star(s, f) #-------------------------------------------------- f.close() print 'wrote file', outfile print '' gravity.stop()
final,dcpu = scatter32(init, kep, gravity, treecheck, gamma, delta_t, t_end) #final,dcpu = scatter32(init, kep, gravity, gravity, # gamma, delta_t, t_end) cpu += dcpu print '' if final.is_over == 0: print 'interaction is not over' else: if final.escaper > 0: print 'escaper =', final.escaper, \ 'sep =', final.separation, \ 'vel =', final.v_rel print 'binary (mass =', final.mbinary, \ 'semi =', final.semimajoraxis, \ 'ecc =', final.eccentricity, ')' else: print 'ionization' if nscatter > 1: print '\n--------------------\n' print 'timing: init', cpu[0], 'evol', cpu[1], 'over', cpu[2], \ 'tree', cpu[3], 'clean', cpu[4] print '' gravity.stop() treecheck.stop() kep.stop()
def run_ph4(initial_file=None, end_time=0 | nbody_system.time, input_delta_t=0.0 | nbody_system.time, input_Delta_t=1.0 | nbody_system.time, input_timestep_parameter=0.0, input_softening_length=-1.0 | nbody_system.length, n_workers=1, use_gpu=1, gpu_worker=1, use_multiples=True, save_restart=False, strict_restart=False): # Read an N-body system from a file and run it to the specified # time using the specified steps. Print log information and # optionally save a restart file after every step. If the # specified time is less than the time in the initial file, don't # take a step, but still print out the log info. (Hence run_ph4 # also functions like Starlab sys_stats.) print "initial_file =", initial_file print "end_time =", end_time.number print "n_workers =", n_workers print "use_gpu =", use_gpu print "use_multiples =", use_multiples print "save_restart =", save_restart print "strict_restart =", strict_restart print "\ninitializing the gravity module" sys.stdout.flush() init_smalln() # Note that there are actually three GPU options: # # 1. use the GPU code and allow GPU use (default) # 2. use the GPU code but disable GPU use (-g) # 3. use the non-GPU code (-G) if gpu_worker == 1: try: gravity = grav(number_of_workers=n_workers, redirection="none", mode="gpu") except Exception as ex: gravity = grav(number_of_workers=n_workers, redirection="none") else: gravity = grav(number_of_workers=n_workers, redirection="none") gravity.initialize_code() gravity.parameters.set_defaults() kep = Kepler(None, redirection="none") kep.initialize_code() stars, time, delta_t, E0, cpu0, multiples_code \ = read_state_from_file(initial_file, gravity, kep) # Allow overrides of the restored data (OK for delta_t, NOT # recommended for timestep_parameter or softening_length). Note # that reading the state also commits the particles, and hence # calculates the initial time steps. Probably should reinitialize # if timestep_parameter or softening_length are changed. TODO if input_delta_t.number > 0: if input_delta_t != delta_t: print 'modifying delta_t from stored', delta_t, \ 'to input', input_delta_t delta_t = input_delta_t else: print "using stored delta_t =", delta_t print input_timestep_parameter print gravity.parameters.timestep_parameter if input_timestep_parameter > 0: if input_timestep_parameter != gravity.parameters.timestep_parameter: print 'modifying timestep_parameter from stored', \ gravity.parameters.timestep_parameter, \ 'to input', input_timestep_parameter gravity.parameters.timestep_parameter \ = input_timestep_parameter else: print 'timestep_parameter =', gravity.parameters.timestep_parameter if input_softening_length.number >= 0: if input_softening_length*input_softening_length \ != gravity.parameters.epsilon_squared: print 'modifying softening_length from stored', \ gravity.parameters.epsilon_squared.sqrt(), \ 'to input', input_softening_length gravity.parameters.epsilon_squared \ = softening_length*softening_length else: print 'softening length =', gravity.parameters.epsilon_squared.sqrt() gravity.parameters.use_gpu = use_gpu gravity.parameters.begin_time = time if 0: print '' print gravity.parameters.begin_time print stars.mass #print stars.position for s in stars: print '%.18e %.18e %.18e' % (s.x.number, s.y.number, s.z.number) print stars.velocity channel = gravity.particles.new_channel_to(stars) if use_multiples: stopping_condition = gravity.stopping_conditions.collision_detection stopping_condition.enable() gravity.parameters.force_sync = 1 # end exactly at the specified time pre = "%%% " print_log(pre, time, multiples_code, E0, cpu0) tsave = time + Delta_t save_file = '' while time < end_time: time += delta_t multiples_code.evolve_model(time) #, callback=handle_callback) # Copy values from the module to the set in memory. channel.copy() # Copy the index (ID) as used in the module to the id field in # memory. The index is not copied by default, as different # codes may have different indices for the same particle and # we don't want to overwrite silently. channel.copy_attribute("index_in_code", "id") # Write log information. print_log(pre, time, multiples_code, E0, cpu0) sys.stdout.flush() # Optionally create a restart file. if save_restart and time >= tsave: #save_file = 't='+'{:07.2f}'.format(time.number) # not in Python 2.6 save_file = 't=%07.2f' % time.number write_state_to_file(time, stars, gravity, multiples_code, save_file, delta_t, E0, cpu0) sys.stdout.flush() tsave += Delta_t if strict_restart: break gravity.stop() kep.stop() stop_smalln() return time, save_file
PossibleHostStars.remove_particle(star) # Create the Planetary Systems in SU Units PSysConverter = nbody_system.nbody_to_si( 2 * np.mean(PossibleHostStars.mass), 2 * np.mean(PossibleHostStars.radius)) kep_p = Kepler(unit_converter=PSysConverter, redirection='none') Starting_Planets = create.planetary_systems_v2( PossibleHostStars, num_psys, Earth=False, Jupiter=True, TestP=False, kepler_worker=kep_p) kep_p.stop() # Set up the Small Scale Converter for Kepler/SmallN SmallScaleConverter = nbody_system.nbody_to_si( 2 * np.mean(Starting_Stars.mass), 2 * np.mean(Starting_Stars.radius)) # Ensuring the Minimum Interaction Radius for Stars is Held for star in Starting_Stars: min_star_radius = 500 | units.AU max_star_radius = 10000 | units.AU if star.radius <= min_star_radius: star.radius = min_star_radius elif star.radius >= max_star_radius: star.radius = max_star_radius # ------------------------------------- # # Setting up Req Particle Subsets # # ------------------------------------- #
def run_ph4(options, time=None, stars=None, mc_root_to_tree=None, randomize=True): infile = options.infile outfile = options.outfile restart_file = options.restart_file number_of_stars = options.N number_of_binaries = options.Nbin end_time = options.t_end | nbody_system.time delta_t = options.delta_t | nbody_system.time n_workers = options.n_workers use_gpu = options.use_gpu gpu_worker = options.gpu_worker salpeter = options.salpeter accuracy_parameter = options.accuracy_parameter softening_length = options.softening_length | nbody_system.length manage_encounters = options.manage_encounters random_seed = options.random_seed if randomize: if random_seed <= 0: numpy.random.seed() random_seed = numpy.random.randint(1, pow(2, 31) - 1) numpy.random.seed(random_seed) print "random seed =", random_seed if infile is not None: print "input file =", infile if restart_file is not None: print "restart file =", restart_file if restart_file is not None and infile is not None: print "restart file overrides input file" print "end_time =", end_time.number print "delta_t =", delta_t.number print "n_workers =", n_workers print "use_gpu =", use_gpu print "manage_encounters =", manage_encounters print "n =", number_of_stars print "nbin=", number_of_binaries print "\ninitializing the gravity module" sys.stdout.flush() init_smalln() # Note that there are actually three GPU options: # # 1. use the GPU code and allow GPU use (default) # 2. use the GPU code but disable GPU use (-g) # 3. use the non-GPU code (-G) if gpu_worker == 1: try: #gravity = GravityModule(number_of_workers = n_workers, # redirection = "xterm") gravity = GravityModule(number_of_workers=n_workers, redirection="none", mode="gpu") except Exception as ex: gravity = GravityModule(number_of_workers=n_workers, redirection="none") else: gravity = GravityModule(number_of_workers=n_workers, redirection="none") gravity.initialize_code() gravity.parameters.set_defaults() if softening_length < 0.0 | nbody_system.length: # Use ~interparticle spacing. Assuming standard units here. TODO eps2 = 0.25*(float(number_of_stars))**(-0.666667) \ | nbody_system.length**2 else: eps2 = softening_length * softening_length print 'softening length =', eps2.sqrt() gravity.parameters.timestep_parameter = accuracy_parameter gravity.parameters.epsilon_squared = eps2 gravity.parameters.use_gpu = use_gpu kep = Kepler(redirection="none") kep.initialize_code() multiples_code = None Xtra = numpy.zeros(2) #----------------------------------------------------------------- if (restart_file is None or not os.path.exists(restart_file + ".stars.hdf5") ) and infile is None and stars is None: print "making a Plummer model" stars = new_plummer_model(number_of_stars) id = numpy.arange(number_of_stars) stars.id = id + 1 print "setting particle masses and radii" if salpeter == 0: print 'equal masses' total_mass = 1.0 | nbody_system.mass scaled_mass = total_mass / number_of_stars else: print 'salpeter mass function' scaled_mass = new_salpeter_mass_distribution_nbody(number_of_stars) stars.mass = scaled_mass print "centering stars" stars.move_to_center() print "scaling stars to virial equilibrium" stars.scale_to_standard( smoothing_length_squared=gravity.parameters.epsilon_squared) time = 0.0 | nbody_system.time total_mass = stars.mass.sum() ke = pa.kinetic_energy(stars) kT = ke / (1.5 * number_of_stars) # Set dynamical radii (assuming virial equilibrium and standard # units). Note that this choice should be refined, and updated # as the system evolves. Probably the choice of radius should be # made entirely in the multiples module. TODO. In these units, # M = 1 and <v^2> = 0.5, so the mean 90-degree turnaround impact # parameter is # # b_90 = G (m_1+m_2) / vrel^2 # = 2 <m> / 2<v^2> # = 2 / N for equal masses # # Taking r_i = m_i / 2<v^2> = m_i in virial equilibrium means # that, approximately, "contact" means a 90-degree deflection (r_1 # + r_2 = b_90). A more conservative choice with r_i less than # this value will isolates encounters better, but also place more # load on the large-N dynamical module. stars.radius = stars.mass.number | nbody_system.length if number_of_binaries > 0: # Turn selected stars into binary components. # Only tested for equal-mass case. added_mass = 0.0 | nbody_system.mass # Work with energies rather than semimajor axes. Emin = 10 * kT Emax = 20 * kT ecc = 0.1 id_count = number_of_stars nbin = 0 for i in range(0, number_of_stars, number_of_stars / number_of_binaries): # Star i is CM, becomes component, add other star at end. nbin += 1 mass = stars[i].mass #new_mass = numpy.random.uniform()*mass # uniform q? new_mass = mass # uniform q? mbin = mass + new_mass fac = new_mass / mbin E = Emin + numpy.random.uniform() * (Emax - Emin) a = 0.5 * nbody_system.G * mass * new_mass / E kep.initialize_from_elements(mbin, a, ecc) dr = quantities.AdaptingVectorQuantity() dr.extend(kep.get_separation_vector()) dv = quantities.AdaptingVectorQuantity() dv.extend(kep.get_velocity_vector()) newstar = datamodel.Particles(1) newstar.mass = new_mass newstar.position = stars[i].position + (1 - fac) * dr newstar.velocity = stars[i].velocity + (1 - fac) * dv newstar.radius = newstar.mass.number | nbody_system.length #newstar.radius = 3.0*stars[i].radius # HACK: try to force collision # stars[i].mass = mass stars[i].position = stars[i].position - fac * dr stars[i].velocity = stars[i].velocity - fac * dv id_count += 1 newstar.id = id_count stars.add_particles(newstar) added_mass += new_mass if nbin >= number_of_binaries: break kep.stop() print 'created', nbin, 'binaries' sys.stdout.flush() stars.mass = stars.mass * total_mass / (total_mass + added_mass) number_of_stars += nbin Xtra = numpy.zeros(2) print "recentering stars" stars.move_to_center() sys.stdout.flush() stars.savepoint(time) print '' print "adding particles" # print stars sys.stdout.flush() gravity.particles.add_particles(stars) gravity.commit_particles() else: print "Restart detected. Loading parameters from restart." new_end = options.t_end stars, time, multiples_code, Xtra = MRest.read_state_from_file( restart_file, gravity, new_smalln, kep) options.t_end = new_end total_mass = stars.mass.sum() ke = pa.kinetic_energy(stars) kT = ke / (1.5 * number_of_stars) # print "IDs:", stars.id.number print '' print "number_of_stars =", number_of_stars print "evolving to time =", end_time.number, \ "in steps of", delta_t.number sys.stdout.flush() # Channel to copy values from the code to the set in memory. channel = gravity.particles.new_channel_to(stars) stopping_condition = gravity.stopping_conditions.collision_detection stopping_condition.enable() # ----------------------------------------------------------------- # Create the coupled code and integrate the system to the desired # time, managing interactions internally. kep = init_kepler(stars[0], stars[1]) if not multiples_code: multiples_code = multiples.Multiples(gravity, new_smalln, kep) multiples_code.neighbor_distance_factor = 1.0 multiples_code.neighbor_veto = True #multiples_code.neighbor_distance_factor = 2.0 #multiples_code.neighbor_veto = True multiples_code.retain_binary_apocenter = False print '' print 'multiples_code.initial_scale_factor =', \ multiples_code.initial_scale_factor print 'multiples_code.neighbor_distance_factor =', \ multiples_code.neighbor_distance_factor print 'multiples_code.neighbor_veto =', \ multiples_code.neighbor_veto print 'multiples_code.final_scale_factor =', \ multiples_code.final_scale_factor print 'multiples_code.initial_scatter_factor =', \ multiples_code.initial_scatter_factor print 'multiples_code.final_scatter_factor =', \ multiples_code.final_scatter_factor print 'multiples_code.retain_binary_apocenter =', \ multiples_code.retain_binary_apocenter # if mc_root_to_tree is not None: # multiples_code.root_to_tree = mc_root_to_tree # print 'multiples code re-loaded with binary trees snapshot' pre = "%%% " E0, cpu0 = print_log(pre, time, multiples_code) while time < end_time: time += delta_t multiples_code.evolve_model(time) # Copy values from the module to the set in memory. channel.copy() # Copy the index (ID) as used in the module to the id field in # memory. The index is not copied by default, as different # codes may have different indices for the same particle and # we don't want to overwrite silently. channel.copy_attribute("index_in_code", "id") print_log(pre, time, multiples_code, E0, cpu0) stars.savepoint(time) MRest.write_state_to_file(time, stars, gravity, multiples_code, options.restart_file, Xtra, backup=1) sys.stdout.flush() #----------------------------------------------------------------- if not outfile is None: # Write data to a file. f = open(outfile, 'w') #-------------------------------------------------- # Need to save top-level stellar data and parameters. # Need to save multiple data and parameters. f.write('%.15g\n' % time.number) for s in multiples_code.stars: write_star(s, f) #-------------------------------------------------- f.close() print 'wrote file', outfile print '' gravity.stop()
class StellarEncounterInHydrodynamics(object): """ Resolves collisions between stars by converting them to SPH models, let them collide in an SPH code, and converting the resulting SPH particle distribution back to a 1D stellar evolution model. Requires a stellar evolution code to supply the internal structure of the stars for the convert_stellar_model_to_SPH routine. Requires a gravity code to set up the initial configuration. The stars in the gravity code have typically already collided, so they are first "evolved" back in time up to a certain separation, assuming Keplerian motion. :argument number_of_particles: Total number of gas particles in the SPH simulation :argument hydrodynamics: SPH code class for the simulation :argument initial_separation: a factor relative to the sum of the radii (1 means in contact, default: 5) """ stellar_evolution_code_required = True gravity_code_required = True def __init__( self, number_of_particles, hydrodynamics, initial_separation=5, relax_sph_models=True, verbose=False, debug=False, hydrodynamics_arguments=dict(), hydrodynamics_parameters=dict(), star_to_sph_arguments=dict(), sph_to_star_arguments=dict(), ): self.number_of_particles = number_of_particles self.hydrodynamics = hydrodynamics self.initial_separation = initial_separation if not relax_sph_models: self.relax = self.no_relax self.verbose = verbose self.debug = debug self.hydrodynamics_arguments = hydrodynamics_arguments self.hydrodynamics_parameters = hydrodynamics_parameters self.star_to_sph_arguments = star_to_sph_arguments self.sph_to_star_arguments = sph_to_star_arguments self.dynamical_timescales_per_step = 1.0 # encounter_is_over check is performed at this interval self.extra_steps_when_encounter_is_over = 3 self.continue_with_kepler = False def handle_collision(self, primary, secondary, stellar_evolution_code=None, gravity_code=None): particles = self.local_copy_of_particles(primary, secondary) self.collect_required_attributes(particles, gravity_code, stellar_evolution_code) self.backtrack_particles(particles) gas_particles = self.convert_stars(particles, stellar_evolution_code) self.simulate_collision(gas_particles) self.models = [ convert_SPH_to_stellar_model(group, **self.sph_to_star_arguments) for group in self.groups_after_encounter ] return self.new_particles_with_internal_structure_from_models() def new_particles_with_internal_structure_from_models(self): def get_internal_structure(set, particle=None): return self.models[(set.key == particle.key).nonzero()[0]] result = Particles(len(self.models)) result.add_function_attribute("get_internal_structure", None, get_internal_structure) result.mass = [ model.dmass.sum().as_quantity_in(self.mass_unit) for model in self.models ] result.radius = [ model.radius[-1].as_quantity_in(self.radius_unit) for model in self.models ] result.position = (self.original_center_of_mass + self.stars_after_encounter.position).as_quantity_in( self.position_unit) result.velocity = (self.original_center_of_mass_velocity + self.stars_after_encounter.velocity).as_quantity_in( self.velocity_unit) return result def local_copy_of_particles(self, primary, secondary): particles = Particles(0) particles.add_particle(primary) particles.add_particle(secondary) return particles def collect_required_attributes(self, particles, gravity_code, stellar_evolution_code): # Collect the required attributes and copy to the particles in memory required_attributes = set( ["mass", "x", "y", "z", "vx", "vy", "vz", "radius"]) required_attributes -= set( particles.get_attribute_names_defined_in_store()) for code in [stellar_evolution_code, gravity_code]: attrs_in_code = required_attributes & set( code.particles.get_attribute_names_defined_in_store()) if len(attrs_in_code) > 0: code.particles.copy_values_of_attributes_to( list(attrs_in_code), particles) required_attributes -= attrs_in_code self.mass_unit = particles.mass.unit self.radius_unit = particles.radius.unit self.position_unit = particles.position.unit self.velocity_unit = particles.velocity.unit self.dynamical_timescale = numpy.pi * ( particles.radius.sum()**3 / (8 * constants.G * particles.total_mass())).sqrt() def start_kepler(self, mass_unit, length_unit): unit_converter = nbody_system.nbody_to_si(mass_unit, length_unit) self.kepler = Kepler(unit_converter, redirection="none" if self.debug else "null") self.kepler.initialize_code() def initialize_binary_in_kepler(self, star_a, star_b): self.kepler.initialize_from_dyn( star_a.mass + star_b.mass, star_a.x - star_b.x, star_a.y - star_b.y, star_a.z - star_b.z, star_a.vx - star_b.vx, star_a.vy - star_b.vy, star_a.vz - star_b.vz) return self.kepler def backtrack_particles(self, particles): self.original_center_of_mass = particles.center_of_mass() self.original_center_of_mass_velocity = particles.center_of_mass_velocity( ) initial_separation = self.initial_separation * particles.radius.sum() if self.verbose: print "Particles at collision:" print particles print "Backtrack particles to initial separation", initial_separation.as_string_in( units.RSun) self.start_kepler(particles.total_mass(), initial_separation) kepler = self.initialize_binary_in_kepler(particles[0], particles[1]) kepler.return_to_radius(initial_separation) self.begin_time = kepler.get_time() particles[1].position = kepler.get_separation_vector() particles[1].velocity = kepler.get_velocity_vector() kepler.advance_to_periastron() self.begin_time -= kepler.get_time() particles[0].position = [0, 0, 0] | units.m particles[0].velocity = [0, 0, 0] | units.m / units.s particles.move_to_center() if self.verbose: print "Backtracking particles done. Initial conditions:" print particles def convert_stars(self, particles, stellar_evolution_code): n_particles = self.divide_number_of_particles(particles) se_colliders = particles.get_intersecting_subset_in( stellar_evolution_code.particles) if self.verbose: print "Converting stars of {0} to SPH models of {1} particles, respectively.".format( particles.mass, n_particles) sph_models = (self.relax( convert_stellar_model_to_SPH(se_colliders[0], n_particles[0], **self.star_to_sph_arguments)), self.relax( convert_stellar_model_to_SPH( se_colliders[1], n_particles[1], **self.star_to_sph_arguments))) gas_particles = Particles() for particle, sph_model in zip(particles, sph_models): sph_model.position += particle.position sph_model.velocity += particle.velocity gas_particles.add_particles(sph_model) if self.verbose: print "Converting stars to SPH particles done" if self.debug: print gas_particles return gas_particles def divide_number_of_particles(self, particles): n1 = int(0.5 + self.number_of_particles * particles[0].mass / particles.total_mass()) return (n1, self.number_of_particles - n1) def relax(self, sph_model): if self.debug: monitor = dict(time=[] | units.day, kinetic=[] | units.J, potential=[] | units.J, thermal=[] | units.J) gas_particles = sph_model.gas_particles hydro = self.new_hydrodynamics(gas_particles) hydro.parameters.artificial_viscosity_alpha = 0.0 # Viscous damping doesn't seem to be very important, but turned off just in case... channel_from_hydro = hydro.gas_particles.new_channel_to(gas_particles) channel_to_hydro = gas_particles.new_channel_to(hydro.gas_particles) dynamical_timescale = numpy.pi * ( gas_particles.total_radius()**3 / (8 * constants.G * gas_particles.total_mass())).sqrt() t_end_in_t_dyn = 2.5 # Relax for this many dynamical timescales n_steps = 100 velocity_damp_factor = 1.0 - ( 2.0 * numpy.pi * t_end_in_t_dyn) / n_steps # Critical damping if self.verbose: print "Relaxing SPH model with {0} for {1} ({2} dynamical timescales).".format( self.hydrodynamics.__name__, (t_end_in_t_dyn * dynamical_timescale).as_string_in(units.day), t_end_in_t_dyn) for i_step, time in enumerate( t_end_in_t_dyn * dynamical_timescale * numpy.linspace(1.0 / n_steps, 1.0, n_steps)): hydro.evolve_model(time) channel_from_hydro.copy_attributes( ["mass", "x", "y", "z", "vx", "vy", "vz", "u"]) gas_particles.position -= gas_particles.center_of_mass() gas_particles.velocity = velocity_damp_factor * ( gas_particles.velocity - gas_particles.center_of_mass_velocity()) channel_to_hydro.copy_attributes(["x", "y", "z", "vx", "vy", "vz"]) if self.debug: K, U, Q = hydro.kinetic_energy, hydro.potential_energy, hydro.thermal_energy print "t, K, U, Q:", time, K, U, Q monitor["time"].append(time) monitor["kinetic"].append(K) monitor["potential"].append(U) monitor["thermal"].append(Q) hydro.stop() if self.debug: energy_evolution_plot(monitor["time"], monitor["kinetic"], monitor["potential"], monitor["thermal"]) return gas_particles def no_relax(self, sph_model): return sph_model.gas_particles def new_hop(self, particles): converter = nbody_system.nbody_to_si(particles.total_mass(), 1.0 | units.RSun) if self.debug: print "Output of Hop is redirected to hop_out.log" options = dict(redirection="file", redirect_file="hop_out.log") else: options = dict() hop = Hop(unit_converter=converter, **options) hop.parameters.number_of_neighbors_for_hop = 100 hop.parameters.saddle_density_threshold_factor = 0.8 hop.parameters.relative_saddle_density_threshold = True return hop def new_hydrodynamics(self, gas_particles): unit_converter = nbody_system.nbody_to_si(gas_particles.total_mass(), self.dynamical_timescale) hydro = self.hydrodynamics(unit_converter, **self.hydrodynamics_arguments) hydro.initialize_code() for par, value in self.hydrodynamics_parameters.iteritems(): setattr(hydro.parameters, par, value) hydro.commit_parameters() hydro.gas_particles.add_particles(gas_particles) hydro.commit_particles() return hydro def simulate_collision(self, gas_particles): self.hop = self.new_hop(gas_particles) hydro = self.new_hydrodynamics(gas_particles) channel = hydro.gas_particles.new_channel_to(gas_particles) if self.verbose: print "Simulating collision with {0} from {1} to {2}.".format( self.hydrodynamics.__name__, self.begin_time.as_string_in(units.day), (self.dynamical_timescales_per_step * self.dynamical_timescale).as_string_in(units.day)) hydro.evolve_model(self.dynamical_timescales_per_step * self.dynamical_timescale - self.begin_time) channel.copy_attributes( ["x", "y", "z", "vx", "vy", "vz", "pressure", "density", "u"]) extra_steps_counter = 0 while True: if self.encounter_is_over(gas_particles): extra_steps_counter += 1 if extra_steps_counter > self.extra_steps_when_encounter_is_over: print "Encounter is over and finished extra steps." break else: print "Encounter is over. Now performing step {0} out of {1} extra steps".format( extra_steps_counter, self.extra_steps_when_encounter_is_over) else: extra_steps_counter = 0 print "Continuing to {0}.".format( (hydro.model_time + self.next_dt + self.begin_time).as_string_in(units.day)) if self.continue_with_kepler: self.evolve_with_kepler(hydro) hydro.evolve_model(hydro.model_time + self.next_dt) channel.copy_attributes( ["x", "y", "z", "vx", "vy", "vz", "pressure", "density", "u"]) hydro.stop() self.hop.stop() self.kepler.stop() def encounter_is_over(self, gas_particles): self.next_dt = self.dynamical_timescales_per_step * self.dynamical_timescale groups = self.group_bound_particles(gas_particles) stars = self.convert_groups_to_stars(groups) self.groups_after_encounter = groups self.stars_after_encounter = stars if len(stars) > 1: # Should do full check for stable binaries, triple, multiples, two escapers, # escaping star + binary, etc. # For now we only check whether the two most massive groups will (re)collide a, b = stars.sorted_by_attribute("mass")[-2:] if self.debug: print "System consists of {0} groups. The two most massive are: {1} and {2}.".format( len(stars), a.mass.as_string_in(units.MSun), b.mass.as_string_in(units.MSun)) if self.binary_will_collide(a, b): return False if self.verbose: print "Encounter is over, {0} stars after encounter.".format( len(groups)) return True def group_bound_particles(self, gas_particles): groups, lost = self.analyze_particle_distribution(gas_particles) while len(lost) > 0: if self.debug: group_plot(groups, lost) previous_number_of_lost_particles = len(lost) groups, lost = self.select_bound_particles(groups, lost) if len(lost) == previous_number_of_lost_particles: break return groups def convert_groups_to_stars(self, groups): stars = Particles(len(groups)) for star, group in zip(stars, groups): star.mass = group.total_mass() star.position = group.center_of_mass() star.velocity = group.center_of_mass_velocity() star.radius = group.LagrangianRadii(mf=[0.9], cm=star.position)[0][0] return stars def analyze_particle_distribution(self, gas_particles): if self.verbose: print "Analyzing particle distribution using Hop" if "density" in gas_particles.get_attribute_names_defined_in_store(): if self.debug: print "Using the original particles' density" self.hop.parameters.outer_density_threshold = 0.5 * gas_particles.density.mean( ) self.hop.particles.add_particles(gas_particles) gas_particles.copy_values_of_attribute_to("density", self.hop.particles) else: if self.debug: print "Using Hop to calculate the density" self.hop.particles.add_particles(gas_particles) self.hop.calculate_densities() self.hop.parameters.outer_density_threshold = 0.5 * self.hop.particles.density.mean( ) self.hop.do_hop() result = [] for group in self.hop.groups(): result.append(group.get_intersecting_subset_in(gas_particles)) lost = self.hop.no_group().get_intersecting_subset_in(gas_particles) self.hop.particles.remove_particles(self.hop.particles) return result, lost def select_bound_particles(self, groups, lost): specific_total_energy_relative_to_group = [] | (units.m / units.s)**2 for group in groups: group_mass = group.total_mass() group_com = group.center_of_mass() group_com_velocity = group.center_of_mass_velocity() specific_total_energy_relative_to_group.append( (lost.velocity - group_com_velocity).lengths_squared() + lost.u - constants.G * group_mass / (lost.position - group_com).lengths()) index_minimum = specific_total_energy_relative_to_group.argmin(axis=0) bound = lost[:0] for i, group in enumerate(groups): bound_to_group = lost[numpy.logical_and( index_minimum == i, specific_total_energy_relative_to_group[i] < 0 | (units.m / units.s)**2)] bound += bound_to_group groups[i] = group + bound_to_group return groups, lost - bound def binary_will_collide(self, a, b): self.continue_with_kepler = False if self.verbose: print "Using Kepler to check whether the two stars will (re)collide." kepler = self.initialize_binary_in_kepler(a, b) true_anomaly = kepler.get_angles()[1] eccentricity = kepler.get_elements()[1] if true_anomaly > 0.0 and eccentricity >= 1.0: if self.verbose: print "Stars are on hyperbolic/parabolic orbits and moving away from each other, interaction is over." return False periastron = kepler.get_periastron() will_collide = periastron < a.radius + b.radius if self.verbose: print "Stars {0} collide. Distance at periastron: {1}, sum of radii: {2}".format( "will" if will_collide else "won't", periastron.as_string_in(units.RSun), (a.radius + b.radius).as_string_in(units.RSun)) if will_collide: # 1) check whether the stars are still relaxing: less than ~3 t_dyn passed since last moment of contact --> relax # 2) check whether the stars are already within 'initial_separation', else skip (dtmax?) kepler.advance_to_periastron() self.next_dt = kepler.get_time( ) + self.dynamical_timescales_per_step * self.dynamical_timescale if self.debug: print "Time to collision: {0}, next_dt: {1}".format( kepler.get_time().as_string_in(units.day), self.next_dt.as_string_in(units.day)) if kepler.get_time( ) > 3 * self.dynamical_timescale and kepler.get_apastron( ) > 2.0 * self.initial_separation * (a.radius + b.radius): # evolve for 3 * self.dynamical_timescale and skip the rest until ~initial_separation kepler.return_to_apastron() kepler.return_to_radius(a.radius + b.radius) if -kepler.get_time( ) > 2.9 * self.dynamical_timescale: # If ~3 t_dyn have passed since the end of the collision if self.verbose: print "~3 t_dyn have passed since the end of the collision -> skip to next collision" self.continue_with_kepler = True kepler.advance_to_apastron() kepler.advance_to_radius(2.0 * self.initial_separation * (a.radius + b.radius)) self.skip_to_relative_position_velocity = ( kepler.get_separation_vector(), kepler.get_velocity_vector()) self.begin_time = kepler.get_time() kepler.advance_to_periastron() self.next_dt = self.dynamical_timescales_per_step * self.dynamical_timescale + kepler.get_time( ) - self.begin_time else: self.next_dt = 3 * self.dynamical_timescale + kepler.get_time( ) return will_collide def evolve_with_kepler(self, hydro): if self.verbose: print "evolve_with_kepler" indices_two_most_massive = self.stars_after_encounter.mass.argsort( )[-2:] groups = [ self.groups_after_encounter[i] for i in indices_two_most_massive ] old_particles = self.stars_after_encounter[indices_two_most_massive] new_particles = Particles(2) new_particles.mass = old_particles.mass new_particles[0].position, new_particles[ 0].velocity = self.skip_to_relative_position_velocity new_particles.move_to_center() for group, old_particle, new_particle in zip(groups, old_particles, new_particles): in_hydro = group.get_intersecting_subset_in(hydro.gas_particles) if self.verbose: print in_hydro.center_of_mass().as_quantity_in( units.RSun), old_particle.position.as_quantity_in( units.RSun), new_particle.position.as_quantity_in( units.RSun) in_hydro.position += new_particle.position - old_particle.position in_hydro.velocity += new_particle.velocity - old_particle.velocity
def get_orbit_ini(m0, m1, peri, ecc, incl, omega, rel_force=0.01, r_disk=50 | units.AU): converter = nbody_system.nbody_to_si(1 | units.MSun, 1 | units.AU) # semi-major axis if ecc != 1.0: semi = peri / (1.0 - ecc) else: semi = 1.0e10 | units.AU # relative position and velocity vectors at the pericenter using kepler kepler = Kepler_twobody(converter) kepler.initialize_code() kepler.initialize_from_elements(mass=(m0 + m1), semi=semi, ecc=ecc, periastron=peri) # at pericenter # moving particle backwards to radius r where: F_m1(r) = rel_force*F_m0(r_disk) #r_disk = peri r_ini = r_disk * (1.0 + numpy.sqrt(m1 / m0) / rel_force) kepler.return_to_radius(radius=r_ini) rl = kepler.get_separation_vector() r = [ rl[0].value_in(units.AU), rl[1].value_in(units.AU), rl[2].value_in( units.AU) ] | units.AU vl = kepler.get_velocity_vector() v = [ vl[0].value_in(units.kms), vl[1].value_in(units.kms), vl[2].value_in( units.kms) ] | units.kms period_kepler = kepler.get_period() time_peri = kepler.get_time() kepler.stop() # rotation of the orbital plane by inclination and argument of periapsis a1 = ([1.0, 0.0, 0.0], [0.0, numpy.cos(incl), -numpy.sin(incl)], [0.0, numpy.sin(incl), numpy.cos(incl)]) a2 = ([numpy.cos(omega), -numpy.sin(omega), 0.0], [numpy.sin(omega), numpy.cos(omega), 0.0], [0.0, 0.0, 1.0]) rot = numpy.dot(a1, a2) r_au = numpy.reshape(r.value_in(units.AU), 3, 1) v_kms = numpy.reshape(v.value_in(units.kms), 3, 1) r_rot = numpy.dot(rot, r_au) | units.AU v_rot = numpy.dot(rot, v_kms) | units.kms bodies = Particles(2) bodies[0].mass = m0 bodies[0].radius = 1.0 | units.RSun bodies[0].position = (0, 0, 0) | units.AU bodies[0].velocity = (0, 0, 0) | units.kms bodies[1].mass = m1 bodies[1].radius = 1.0 | units.RSun bodies[1].x = r_rot[0] bodies[1].y = r_rot[1] bodies[1].z = r_rot[2] bodies[1].vx = v_rot[0] bodies[1].vy = v_rot[1] bodies[1].vz = v_rot[2] bodies.age = 0.0 | units.yr bodies.move_to_center() print "\t r_rel_ini = ", r_rot.in_(units.AU) print "\t v_rel_ini = ", v_rot.in_(units.kms) print "\t time since peri = ", time_peri.in_(units.yr) a_orbit, e_orbit, p_orbit = orbital_parameters(r_rot, v_rot, (m0 + m1)) print "\t a = ", a_orbit.in_( units.AU), "\t e = ", e_orbit, "\t period = ", p_orbit.in_(units.yr) return bodies, time_peri
def get_orbit_ini(m0, m1, peri, ecc, incl, omega, rel_force=0.01, r_disk=50|units.AU): converter=nbody_system.nbody_to_si(1|units.MSun,1|units.AU) # semi-major axis if ecc!=1.0: semi = peri/(1.0-ecc) else: semi = 1.0e10 | units.AU # relative position and velocity vectors at the pericenter using kepler kepler = Kepler_twobody(converter) kepler.initialize_code() kepler.initialize_from_elements(mass=(m0+m1), semi=semi, ecc=ecc, periastron=peri) # at pericenter # moving particle backwards to radius r where: F_m1(r) = rel_force*F_m0(r_disk) #r_disk = peri r_ini = r_disk*(1.0 + numpy.sqrt(m1/m0)/rel_force) kepler.return_to_radius(radius=r_ini) rl = kepler.get_separation_vector() r = [rl[0].value_in(units.AU), rl[1].value_in(units.AU), rl[2].value_in(units.AU)] | units.AU vl = kepler.get_velocity_vector() v = [vl[0].value_in(units.kms), vl[1].value_in(units.kms), vl[2].value_in(units.kms)] | units.kms period_kepler = kepler.get_period() time_peri = kepler.get_time() kepler.stop() # rotation of the orbital plane by inclination and argument of periapsis a1 = ([1.0, 0.0, 0.0], [0.0, numpy.cos(incl), -numpy.sin(incl)], [0.0, numpy.sin(incl), numpy.cos(incl)]) a2 = ([numpy.cos(omega), -numpy.sin(omega), 0.0], [numpy.sin(omega), numpy.cos(omega), 0.0], [0.0, 0.0, 1.0]) rot = numpy.dot(a1,a2) r_au = numpy.reshape(r.value_in(units.AU), 3, 1) v_kms = numpy.reshape(v.value_in(units.kms), 3, 1) r_rot = numpy.dot(rot, r_au) | units.AU v_rot = numpy.dot(rot, v_kms) | units.kms bodies = Particles(2) bodies[0].mass = m0 bodies[0].radius = 1.0|units.RSun bodies[0].position = (0,0,0) | units.AU bodies[0].velocity = (0,0,0) | units.kms bodies[1].mass = m1 bodies[1].radius = 1.0|units.RSun bodies[1].x = r_rot[0] bodies[1].y = r_rot[1] bodies[1].z = r_rot[2] bodies[1].vx = v_rot[0] bodies[1].vy = v_rot[1] bodies[1].vz = v_rot[2] bodies.age = 0.0 | units.yr bodies.move_to_center() print "\t r_rel_ini = ", r_rot.in_(units.AU) print "\t v_rel_ini = ", v_rot.in_(units.kms) print "\t time since peri = ", time_peri.in_(units.yr) a_orbit, e_orbit, p_orbit = orbital_parameters(r_rot, v_rot, (m0+m1)) print "\t a = ", a_orbit.in_(units.AU), "\t e = ", e_orbit, "\t period = ", p_orbit.in_(units.yr) return bodies, time_peri
def get_bodies_in_orbit(m0, m_ffp, m_bp, a_bp, e_bp, phi_bp, inc_bp, lan_bp, b_ffp, r_inf): #Bodies bodies = Particles() ##Get BP in orbit #Binary star_planet = new_binary_from_orbital_elements(m0, m_bp, a_bp, e_bp, true_anomaly=phi_bp, inclination = inc_bp, longitude_of_the_ascending_node = lan_bp) #Planet attributes star_planet.eccentricity = e_bp star_planet.semimajoraxis = a_bp #Center on the star star_planet.position -= star_planet[0].position star_planet.velocity -= star_planet[0].velocity cm_p = star_planet.center_of_mass() cm_v = star_planet.center_of_mass_velocity() ##Get FFP in orbit #Particle set m0_ffp = Particles(2) #Zeros and parabolic velocity zero_p = 0.0 | nbody_system.length zero_v = 0.0 | nbody_system.speed parabolic_velocity = get_parabolic_velocity(m0, m_bp, b_ffp, r_inf) #Central star m0_ffp[0].mass = m0 m0_ffp[0].position = (zero_p,zero_p,zero_p) m0_ffp[0].velocity = (zero_v,zero_v,zero_v) #Free-floating planet m0_ffp[1].mass = m_ffp m0_ffp[1].position = (-r_inf+cm_p[0], b_ffp+cm_p[1], cm_p[2]) m0_ffp[1].velocity = (parabolic_velocity+cm_v[0], cm_v[1], cm_v[2]) #To find the orbital period of the BP G = (1.0 | nbody_system.length**3 * nbody_system.time**-2 * nbody_system.mass**-1) orbital_period_bp = 2*math.pi*((a_bp**3)/(G*m0)).sqrt() #To find the distance and time to periastron kep = Kepler() kep.initialize_code() star_planet_as_one = Particles(1) star_planet_as_one.mass = m0 + m_bp star_planet_as_one.position = cm_p star_planet_as_one.velocity = cm_v kepler_bodies = Particles() kepler_bodies.add_particle(star_planet_as_one[0]) kepler_bodies.add_particle(m0_ffp[1]) kep.initialize_from_particles(kepler_bodies) kep.advance_to_periastron() time_pericenter = kep.get_time() kep.stop() binary = [star_planet_as_one[0], m0_ffp[1]] sma, e, inclination, long_asc_node, arg_per = my_orbital_elements_from_binary(binary) m0_ffp.eccentricity = e m0_ffp.semimajoraxis = sma #Adding bodies. Order: star, ffp, bp bodies.add_particle(m0_ffp[0]) bodies.add_particle(m0_ffp[1]) bodies.add_particle(star_planet[1]) return bodies, time_pericenter, orbital_period_bp
def integrate_system(N, t_end, seed=None): # Initialize an N-body module and load a stellar system. mass = N | units.MSun length = 1 | units.parsec converter = nbody_system.nbody_to_si(mass, length) gravity = ph4(convert_nbody=converter) gravity.initialize_code() gravity.parameters.set_defaults() gravity.parameters.epsilon_squared = (0.0 | units.parsec)**2 if seed is not None: numpy.random.seed(seed) stars = new_plummer_model(N, convert_nbody=converter) stars.mass = mass / N stars.scale_to_standard( convert_nbody=converter, smoothing_length_squared=gravity.parameters.epsilon_squared) # Star IDs are important, as they are used in multiples bookkeeping. id = numpy.arange(N) stars.id = id + 1 # Set dynamical radii. #stars.radius = 0.5/N | units.parsec stars.radius = 2000 | units.AU gravity.particles.add_particles(stars) # Add Planets do_planets = True if do_planets: systems_SI = create.planetary_systems(stars, N - 1, 'test_planets', Jupiter=True) gravity.particles.add_particles(systems_SI) # Enable collision detection. stopping_condition = gravity.stopping_conditions.collision_detection stopping_condition.enable() SmallScaleConverter = nbody_system.nbody_to_si(2 * mass / N, 4000 | units.AU) # Define the small-N code. init_smalln(SmallScaleConverter) # Define a Kepler module. kep = Kepler(unit_converter=SmallScaleConverter) kep.initialize_code() # Create the multiples module. global multiples_code multiples_code = multiples2.Multiples(gravity, new_smalln, kep, constants.G) multiples_code.neighbor_perturbation_limit = 0.05 multiples_code.neighbor_veto = True multiples_code.global_debug = 1 # 0: no output from multiples # 1: minimal output # 2: debugging output # 3: even more output # Setting Up the Encounter Handler encounters = EncounterHandler() multiples_code.callback = encounters.handle_encounter_v3 # Print selected multiples settings. print '' print 'multiples_code.neighbor_veto =', \ multiples_code.neighbor_veto print 'multiples_code.neighbor_perturbation_limit =', \ multiples_code.neighbor_perturbation_limit print 'multiples_code.retain_binary_apocenter =', \ multiples_code.retain_binary_apocenter print 'multiples_code.wide_perturbation_limit =', \ multiples_code.wide_perturbation_limit # Advance the system. time = numpy.sqrt(length**3 / (constants.G * mass)) print '\ntime unit =', time.in_(units.Myr) E0 = print_diagnostics(multiples_code) dt = 0.1 | units.Myr t_current = 0.0 | units.Myr #gravity.particles[-1].mass += 1 | units.MSun while t_current <= t_end: t_current += dt multiples_code.evolve_model(t_current) print gravity.particles.index_in_code #, gravity.particles[0].x #print multiples_code.particles[-1].key, multiples_code.particles[-1].x print multiples_code._inmemory_particles.id, #multiples_code._inmemory_particles[-1].x #print stars[0].id # Testing the Multiples & Gravity Particle Set Differences #test_psystem_id = systems_SI[0].host_star #test_psystem_hs_grav = [star.x for star in gravity.particles if star.index_in_code == test_psystem_id] #test_psystem_hs_mult = [star.x for star in multiples_code.particles if star.idex_in_code == test_psystem_id] #print "-----------------------------" #print "X Position in PH4", test_pystem_hs_grav.in_(units.parsec) #print "X Position in Multiples", test_psystem_hs_mult.in_(units.parsec) #print "Difference in Particle Sets", test_pystem_hs_grav.in_(units.parsec) - test_psystem_hs_mult.in_(units.parsec) #print "-----------------------------" print_diagnostics(multiples_code, E0) gravity.stop() kep.stop() stop_smalln()
def get_planetary_systems_from_set(bodies, converter=None, RelativePosition=False): # Initialize Kepler if converter == None: converter = nbody_system.nbody_to_si( bodies.mass.sum(), 2 * np.max(bodies.radius.number) | bodies.radius.unit) kep_p = Kepler(unit_converter=converter, redirection='none') kep_p.initialize_code() kep_s = Kepler(unit_converter=converter, redirection='none') kep_s.initialize_code() # Seperate Out Planets and Stars from Bodies stars, planets = util.get_stars(bodies), util.get_planets(bodies) num_stars, num_planets = len(stars), len(planets) # Initialize the Dictionary that Contains all Planetary Systems systems = {} # Start Looping Through Stars to Find Bound Planets for star in stars: system_id = star.id #star.semimajor_axis, star.eccentricity, star.period, star.true_anomaly, star.mean_anomaly, star.kep_energy, star.angular_momentum = \ # None, None, None, None, None, None, None current_system = systems.setdefault(system_id, Particles()) current_system.add_particle(star) for planet in planets: total_mass = star.mass + planet.mass kep_pos = star.position - planet.position kep_vel = star.velocity - planet.velocity kep_p.initialize_from_dyn(total_mass, kep_pos[0], kep_pos[1], kep_pos[2], kep_vel[0], kep_vel[1], kep_vel[2]) a_p, e_p = kep_p.get_elements() if e_p < 1.0: # Check to See if The Stellar System is a Binary # Note: Things get complicated if it is ... noStellarHeirarchy = False for other_star in (stars - star): kep_s.initialize_from_dyn( star.mass + other_star.mass, star.x - other_star.x, star.y - other_star.y, star.z - other_star.z, star.vx - other_star.vx, star.vy - other_star.vy, star.vz - other_star.vz) a_s, e_s = kep_s.get_elements() r_apo = kep_s.get_apastron() HillR = util.calc_HillRadius(a_s, e_s, other_star.mass, star.mass) if e_s >= 1.0 or HillR < r_apo: noStellarHeirarchy = True else: noStellarHeirarchy = False if noStellarHeirarchy: # Get Additional Information on Orbit planet.semimajor_axis = a_p planet.eccentricity = e_p planet.period = kep_p.get_period() planet.true_anomaly, planet.mean_anomaly = kep_p.get_angles( ) #planet.kep_energy, planet.angular_momentum = kep_p.get_integrals() # Add the Planet to the System Set current_system.add_particle(planet) else: # Handling for Planetary Systems in Stellar Heirarchical Structures # Note: This is empty for now, maybe consider doing it by the heaviest bound stellar object as the primary. pass else: continue kep_p.stop() kep_s.stop() return systems
def test11(self): """ testing orbital_elements_for_rel_posvel_arrays for unbound orbits """ from amuse.community.kepler.interface import Kepler numpy.random.seed(66) N = 10 mass_sun = 1. | units.MSun mass1 = numpy.ones(N) * mass_sun mass2 = numpy.zeros(N) | units.MSun semi_major_axis=-1000.*(random.random(N)) | units.AU eccentricity = (1.+random.random(N))*10.-9. inclination = numpy.pi*random.random(N) longitude_of_the_ascending_node = 2.*numpy.pi*random.random(N)-numpy.pi argument_of_periapsis = 2.*numpy.pi*random.random(N)-numpy.pi # kepler.initialize_from_elements initializes orbits with mean_anomaly=0 and true_anomaly=0 true_anomaly = 0.*(360.*random.random(N)-180.) comets = datamodel.Particles(N) converter = nbody_system.nbody_to_si(1|units.MSun,1|units.AU) kepler = Kepler(converter) kepler.initialize_code() for i,arg in enumerate(zip(mass1,mass2,semi_major_axis,eccentricity,true_anomaly,inclination, longitude_of_the_ascending_node,argument_of_periapsis)): kepler.initialize_from_elements(mass=(mass1[i]+mass2[i]), semi=semi_major_axis[i], ecc=eccentricity[i]) ri = kepler.get_separation_vector() vi = kepler.get_velocity_vector() om = longitude_of_the_ascending_node[i] w = argument_of_periapsis[i] incl = inclination[i] a1 = ([numpy.cos(om), -numpy.sin(om), 0.0], [numpy.sin(om), numpy.cos(om), 0.0], [0.0, 0.0, 1.0]) a2 = ([1.0, 0.0, 0.0], [0.0, numpy.cos(incl), -numpy.sin(incl)], [0.0, numpy.sin(incl), numpy.cos(incl)]) a3 = ([numpy.cos(w), -numpy.sin(w), 0.0], [numpy.sin(w), numpy.cos(w), 0.0], [0.0, 0.0, 1.0]) A = numpy.dot(numpy.dot(a1,a2),a3) r_vec = numpy.dot(A,numpy.reshape(ri,3,1)) v_vec = numpy.dot(A,numpy.reshape(vi,3,1)) r = (0.0, 0.0, 0.0) | units.AU v = (0.0, 0.0, 0.0) | (units.AU / units.day) r[0] = r_vec[0] r[1] = r_vec[1] r[2] = r_vec[2] v[0] = v_vec[0] v[1] = v_vec[1] v[2] = v_vec[2] comets[i].mass = mass2[i] comets[i].position = r_vec comets[i].velocity = v_vec kepler.stop() semi_major_axis_ext, eccentricity_ext, ta_ext, inclination_ext, \ longitude_of_the_ascending_node_ext, argument_of_periapsis_ext = \ orbital_elements(comets.position, comets.velocity, comets.mass + mass_sun, G=constants.G) self.assertAlmostEqual(semi_major_axis,semi_major_axis_ext.in_(units.AU)) self.assertAlmostEqual(eccentricity,eccentricity_ext) self.assertAlmostEqual(inclination,inclination_ext) self.assertAlmostEqual(longitude_of_the_ascending_node,longitude_of_the_ascending_node_ext) self.assertAlmostEqual(argument_of_periapsis,argument_of_periapsis_ext) self.assertAlmostEqual(true_anomaly,ta_ext)
def get_heirarchical_systems_from_set(bodies, kepler_workers=None, converter=None, RelativePosition=False): # Initialize Kepler if kepler_workers == None: if converter == None: converter = nbody_system.nbody_to_si( bodies.mass.sum(), 2 * np.max(bodies.radius.number) | bodies.radius.unit) kep_p = Kepler(unit_converter=converter, redirection='none') kep_p.initialize_code() kep_s = Kepler(unit_converter=converter, redirection='none') kep_s.initialize_code() else: kep_p = kepler_workers[0] kep_s = kepler_workers[1] # Seperate Out Planets and Stars from Bodies stars, planets = util.get_stars(bodies), util.get_planets(bodies) num_stars, num_planets = len(stars), len(planets) # Initialize the Dictionary that Contains all Planetary Systems systems = {} # Initialize the List Used to Check Star IDs Against Already Classified Binaries binary_ids = [] # Find Nearest Neighbors of the Set closest_neighbours = stars.nearest_neighbour() # Start Looping Through Stars to Find Bound Planets for index, star in enumerate(stars): # If the star is already in Binary_IDs, just go to the next star. if star.id in binary_ids: continue # If not, Set the System ID and Set-up Data Structure. system_id = star.id current_system = systems.setdefault(system_id, Particles()) current_system.add_particle(star) noStellarHierarchy = False # If there is only one stars, there is obviously no stellar heirarchy in # the encounter that is occuring. if len(stars) == 1: noStellarHierarchy = True if not noStellarHierarchy: # Check to see if the Nearest Neighbor is Mutual star_neighbour_id = closest_neighbours[index].id neighbour_neighbour_id = closest_neighbours[ stars.id == star_neighbour_id].id[0] for other_star in (stars - star): # Check to see if the two stars are bound. kep_s.initialize_from_dyn( star.mass + other_star.mass, star.x - other_star.x, star.y - other_star.y, star.z - other_star.z, star.vx - other_star.vx, star.vy - other_star.vy, star.vz - other_star.vz) a_s, e_s = kep_s.get_elements() print(star.id, other_star.id, e_s) # If they ARE NOT bound ... if e_s >= 1.0: noStellarHierarchy = True # If they ARE bound ... else: # If the star is the star's neighbour's neighbour and visa-versa, then proceed. print(star.id, other_star.id, star_neighbour_id, neighbour_neighbour_id) if star.id == neighbour_neighbour_id and other_star.id == star_neighbour_id: noStellarHierarchy = False print("Binary composed of Star", star.id, "and Star", other_star.id, "has been detected!") current_system.add_particle(other_star) binary_ids.append(star.id) binary_ids.append(other_star.id) else: print( "!!! Alert: Bound Stars are not closest neighbours ..." ) print("!!! Current Star:", star.id, "| Other Star:", other_star.id) print("!!! CS's Neighbour:", star_neighbour_id, \ "| CS's Neighbour's Neighbour:", neighbour_neighbour_id) checked_planet_ids = [] for KeyID in systems.keys(): current_system = systems[KeyID] sys_stars = util.get_stars(current_system) noStellarHierarchy = False # If there is only one stars, there is obviously no stellar heirarchy in # the encounter that is occuring. if len(sys_stars) == 1: noStellarHierarchy = True for planet in planets: if planet.id in checked_planet_ids: continue star = sys_stars[sys_stars.id == KeyID][0] total_mass = star.mass + planet.mass kep_pos = star.position - planet.position kep_vel = star.velocity - planet.velocity kep_p.initialize_from_dyn(total_mass, kep_pos[0], kep_pos[1], kep_pos[2], kep_vel[0], kep_vel[1], kep_vel[2]) a_p, e_p = kep_p.get_elements() P_p = kep_p.get_period() Ta_p, Ma_p = kep_p.get_angles() host_star_id = star.id if e_p < 1.0: # Check to See if The Planetary System is tied to a Stellar Binary # Note: Things get complicated if it is ... if noStellarHierarchy: # Get Additional Information on Orbit planet.semimajor_axis = a_p planet.eccentricity = e_p planet.period = P_p planet.true_anomaly = Ta_p planet.mean_anomaly = Ma_p planet.host_star = star.id # Add the Planet to the System Set current_system.add_particle(planet) else: # Handling for Planetary Systems in Stellar Heirarchical Structures # Note: We check to see which other star in the current systems # have a better boundness with the planet and choose that # as the new host star. for other_star in sys_stars - star: total_mass = other_star.mass + planet.mass kep_pos = other_star.position - planet.position kep_vel = other_star.velocity - planet.velocity kep_p.initialize_from_dyn(total_mass, kep_pos[0], kep_pos[1], kep_pos[2], kep_vel[0], kep_vel[1], kep_vel[2]) a_p2, e_p2 = kep_p.get_elements() # Check to see if the planet is more bound to 'star' or # 'other_star'. If its more bound to 'other_star', # set the attributes to the more bound object. This will # replace *_p with the better values with each loop. if e_p2 < e_p: a_p = a_p2 e_p = e_p2 P_p = kep_p.get_period() Ta_p, Ma_p = kep_p.get_angles() host_star_id = other_star.id planet.semimajor_axis = a_p planet.eccentricity = e_p planet.period = P_p planet.true_anomaly = Ta_p planet.mean_anomaly = Ma_p planet.host_star = host_star_id # Add the Planet to the System Set current_system.add_particle(planet) checked_planet_ids.append(planet.id) elif not noStellarHierarchy: # Handling for Planetary Systems in Stellar Heirarchical Structures # Note: We check to see which other star in the current systems # have a better boundness with the planet and choose that # as the new host star. for other_star in sys_stars - star: total_mass = other_star.mass + planet.mass kep_pos = other_star.position - planet.position kep_vel = other_star.velocity - planet.velocity kep_p.initialize_from_dyn(total_mass, kep_pos[0], kep_pos[1], kep_pos[2], kep_vel[0], kep_vel[1], kep_vel[2]) a_p2, e_p2 = kep_p.get_elements() # Check to see if the planet is more bound to 'star' or # 'other_star'. If its more bound to 'other_star', # set the attributes to the more bound object. This will # replace *_p with the better values with each loop. if e_p2 < e_p: a_p = a_p2 e_p = e_p2 P_p = kep_p.get_period() Ta_p, Ma_p = kep_p.get_angles() host_star_id = other_star.id planet.semimajor_axis = a_p planet.eccentricity = e_p planet.period = P_p planet.true_anomaly = Ta_p planet.mean_anomaly = Ma_p planet.host_star = host_star_id # Add the Planet to the System Set current_system.add_particle(planet) else: print( "!!! Alert: Planet is not bound nor is it bound to any other star." ) if kepler_workers == None: kep_p.stop() kep_s.stop() return systems
def CutOrAdvance(enc_bodies, primary_sysID, converter=None): bodies = enc_bodies.copy() if converter==None: converter = nbody_system.nbody_to_si(bodies.mass.sum(), 2 * np.max(bodies.radius.number) | bodies.radius.unit) systems = stellar_systems.get_heirarchical_systems_from_set(bodies, converter=converter, RelativePosition=False) # Deal with Possible Key Issues with Encounters with 3+ Star Particles Being Run More than Other Systems ... if int(primary_sysID) not in systems.keys(): print "...: Error: Previously run binary system has been found! Not running this system ..." print primary_sysID print systems.keys() print "---------------------------------" return None # As this function is pulling from Multiples, there should never be more or less than 2 "Root" Particles ... if len(systems) != 2: print "...: Error: Encounter has more roots than expected! Total Root Particles:", len(systems) print bodies print "---------------------------------" return None # Assign the Primary System to #1 and Perturbing System to #2 sys_1 = systems[int(primary_sysID)] secondary_sysID = [key for key in systems.keys() if key!=int(primary_sysID)][0] sys_2 = systems[secondary_sysID] print 'All System Keys:', systems.keys() print 'Primary System Key:', primary_sysID print 'System 1 IDs:', sys_1.id print 'System 2 IDs:', sys_2.id # Calculate Useful Quantities mass_ratio = sys_2.mass.sum()/sys_1.mass.sum() total_mass = sys_1.mass.sum() + sys_2.mass.sum() rel_pos = sys_1.center_of_mass() - sys_2.center_of_mass() rel_vel = sys_1.center_of_mass_velocity() - sys_2.center_of_mass_velocity() # Initialize Kepler Worker kep = Kepler(unit_converter = converter, redirection = 'none') kep.initialize_code() kep.initialize_from_dyn(total_mass, rel_pos[0], rel_pos[1], rel_pos[2], rel_vel[0], rel_vel[1], rel_vel[2]) # Check to See if the Periastron is within the Ignore Distance for 10^3 Perturbation p = kep.get_periastron() ignore_distance = mass_ratio**(1./3.) * 600 | units.AU if p > ignore_distance: print "Encounter Ignored due to Periastron of", p.in_(units.AU), "and an IgnoreDistance of",ignore_distance kep.stop() print "---------------------------------" return None # Move the Particles to be Relative to their Respective Center of Mass cm_sys_1, cm_sys_2 = sys_1.center_of_mass(), sys_2.center_of_mass() cmv_sys_1, cmv_sys_2 = sys_1.center_of_mass_velocity(), sys_2.center_of_mass_velocity() for particle in sys_1: particle.position -= cm_sys_1 particle.velocity -= cmv_sys_1 for particle in sys_2: particle.position -= cm_sys_2 particle.velocity -= cmv_sys_2 # Check to See if the Planets are Closer than the Ignore Distance # Note: This shouldn't happen in the main code, but this prevents overshooting the periastron in debug mode. if kep.get_separation() > ignore_distance: kep.advance_to_radius(ignore_distance) # Advance the Center of Masses to the Desired Distance in Reduced Mass Coordinates x, y, z = kep.get_separation_vector() rel_pos_f = rel_pos.copy() rel_pos_f[0], rel_pos_f[1], rel_pos_f[2] = x, y, z vx, vy, vz = kep.get_velocity_vector() rel_vel_f = rel_vel.copy() rel_vel_f[0], rel_vel_f[1], rel_vel_f[2] = vx, vy, vz # Transform to Absolute Coordinates from Kepler Reduced Mass Coordinates cm_pos_1, cm_pos_2 = sys_2.mass.sum() * rel_pos_f / total_mass, -sys_1.mass.sum() * rel_pos_f / total_mass cm_vel_1, cm_vel_2 = sys_2.mass.sum() * rel_vel_f / total_mass, -sys_1.mass.sum() * rel_vel_f / total_mass # Move the Particles to the New Postions of their Respective Center of Mass for particle in sys_1: particle.position += cm_pos_1 particle.velocity += cm_vel_1 for particle in sys_2: particle.position += cm_pos_2 particle.velocity += cm_vel_2 # Stop Kepler and Return the Systems as a Particle Set kep.stop() # Collect the Collective Particle Set to be Returned Back final_set = Particles() final_set.add_particles(sys_1) final_set.add_particles(sys_2) print "---------------------------------" return final_set
def compress_binary_components(comp1, comp2, scale): # Compress the two-body system consisting of comp1 and comp2 to # lie within distance scale of one another. pos1 = comp1.position pos2 = comp2.position sep12 = ((pos2 - pos1)**2).sum() if sep12 > scale * scale: print('\ncompressing components', int(comp1.id.number), \ 'and', int(comp2.id.number), 'to separation', scale.number) sys.stdout.flush() mass1 = comp1.mass mass2 = comp2.mass total_mass = mass1 + mass2 vel1 = comp1.velocity vel2 = comp2.velocity cmpos = (mass1 * pos1 + mass2 * pos2) / total_mass cmvel = (mass1 * vel1 + mass2 * vel2) / total_mass # For now, create and delete a temporary kepler # process to handle the transformation. Obviously # more efficient to define a single kepler at the # start of the calculation and reuse it. kep = Kepler(redirection="none") kep.initialize_code() mass = comp1.mass + comp2.mass rel_pos = pos2 - pos1 rel_vel = vel2 - vel1 kep.initialize_from_dyn(mass, rel_pos[0], rel_pos[1], rel_pos[2], rel_vel[0], rel_vel[1], rel_vel[2]) M, th = kep.get_angles() a, e = kep.get_elements() if e < 1: peri = a * (1 - e) apo = a * (1 + e) else: peri = a * (e - 1) apo = 2 * a # OK - used ony to reset scale limit = peri + 0.01 * (apo - peri) if scale < limit: scale = limit if M < 0: # print 'approaching' kep.advance_to_periastron() kep.advance_to_radius(limit) else: # print 'receding' if kep.get_separation() < scale: kep.advance_to_radius(limit) else: kep.return_to_radius(scale) # a,e = kep.get_elements() # r = kep.get_separation() # E,J = kep.get_integrals() # print 'kepler: a,e,r =', a.number, e.number, r.number # print 'E, J =', E, J # Note: if periastron > scale, we are now just past periastron. new_rel_pos = kep.get_separation_vector() new_rel_vel = kep.get_velocity_vector() kep.stop() # Enew = 0 # r2 = 0 # for k in range(3): # Enew += 0.5*(new_rel_vel[k].number)**2 # r2 += (new_rel_pos[k].number)**2 # rnew = math.sqrt(r2) # Enew -= mass.number/r1 # print 'E, Enew, rnew =', E.number, E1, r1 # Problem: the vectors returned by kepler are lists, # not numpy arrays, and it looks as though we can say # comp1.position = pos, but not comp1.position[k] = # xxx, as we'd like... Also, we don't know how to # copy a numpy array with units... TODO newpos1 = pos1 - pos1 # stupid trick to create zero vectors newpos2 = pos2 - pos2 # with the proper form and units... newvel1 = vel1 - vel1 newvel2 = vel2 - vel2 frac2 = mass2 / total_mass for k in range(3): dxk = new_rel_pos[k] dvk = new_rel_vel[k] newpos1[k] = cmpos[k] - frac2 * dxk newpos2[k] = cmpos[k] + (1 - frac2) * dxk newvel1[k] = cmvel[k] - frac2 * dvk newvel2[k] = cmvel[k] + (1 - frac2) * dvk # Perform the changes to comp1 and comp2, and recursively # transmit them to the (currently absolute) coordinates of # all lower components. offset_particle_tree(comp1, newpos1 - pos1, newvel1 - vel1) offset_particle_tree(comp2, newpos2 - pos2, newvel2 - vel2)