From 34eb529dc2fed969ca44969aa89bb1588423e8a7 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Fri, 3 Jul 2020 10:18:18 -0400 Subject: [PATCH 001/130] Importing functions from notebook. --- src/tycho/enc_patching.py | 234 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 src/tycho/enc_patching.py diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py new file mode 100644 index 0000000..fd787fe --- /dev/null +++ b/src/tycho/enc_patching.py @@ -0,0 +1,234 @@ +from tycho import * +from tycho import stellar_systems +from amuse import * +import numpy as np +import matplotlib.pyplot as plt +from amuse.units import units + +import hashlib + +from amuse.community.secularmultiple.interface import SecularMultiple +from amuse.datamodel.trees import BinaryTreesOnAParticleSet + +set_printing_strategy("custom", preferred_units = [units.MSun, units.AU, units.day, units.deg], precision = 6, prefix = "", separator = "[", suffix = "]") + +def get_node_of_leaf(particle_set, chosen_id): + '''Retrieves the node of the desired leaf particle given its ID.''' + tree = BinaryTreesOnAParticleSet(particle_set, 'child1', 'child2') + for root in tree.iter_roots(): + print("Current Root ID:",root.particle.id) + print("List of all Leaves of the Tree:",root.get_leafs_subset().id) + if chosen_id in root.get_leafs_subset().id: + for node in root.iter_inner_nodes(): + if node.child1.id == chosen_id or node.child2.id == chosen_id: + print('Chosen Node and its Children:', node.id, node.child1.id, node.child2.id) + return node + else: + print('!!! Error: Could not find desired leaf in the tree.') + return None + +def get_root_of_leaf(particle_set, chosen_id): + '''Retrieves the root of the desired leaf particle given its ID.''' + tree = BinaryTreesOnAParticleSet(particle_set, 'child1', 'child2') + for root in tree.iter_roots(): + leaves_id = root.get_leafs_subset().id + if chosen_id in leaves_id: + return root.particle + + from amuse.ext.orbital_elements import get_orbital_elements_from_binary + +def get_physical_radius(particle): + try: + radius = particle.physical_radius + return radius + except: + print('No physical Radius Given, Applying Estimate!') + if particle.type == 'planet': + return 1 | units.RJupiter + if particle.type == 'star': + return 1 | units.RSun + + +def get_full_hierarchical_structure(bodies, RelativePosition=False): + hierarchical_set = Particles() + # Calculate Distances to All Bodies for Each Body + for index, body in enumerate(bodies): + try: + if body.id in hierarchical_set.id: + print("Body Already Added:", body.id) + continue + except: + print('First Pass') + pass + list_of_sqd = bodies.distances_squared(body).number + for nonzero_sqd in list_of_sqd[np.nonzero(list_of_sqd)]: + print("Finding closet Neighbor for ID:", body.id) + # Assign the Closest Partner + closest_partner = bodies[[i for i, j in enumerate(list_of_sqd) if j == nonzero_sqd]] + print("Closest Neighbor is:", closest_partner.id) + + # Check if closest_partner is in a binary already + #print(index) + if index != 0: + node = get_root_of_leaf(hierarchical_set, closest_partner.id) + if node != None: + print('Heirarchical Set when finding nodes:',[x.id for x in hierarchical_set if x.is_binary==True]) + closest_partner = node + print("Closest Neighbor is:", closest_partner.id) + + # Calculate the Orbital Elements + k_set = Particles() + k_set.add_particle(body.copy()) + k_set.add_particle(closest_partner.copy()) + k_set_sorted = k_set.sorted_by_attribute('mass')[::-1] # Ensures Heaviest is First + print(k_set.id) + (mass1, mass2, semimajor_axis, eccentricity, \ + true_anomaly, inclination, long_asc_node, arg_per) \ + = get_orbital_elements_from_binary(k_set_sorted, G=constants.G) + # Determine if 'body' orbits 'closest_partner' + if eccentricity < 1.0: + bigbro = k_set.sorted_by_attribute('mass')[1].copy() #Child1 is Always Heaviest + lilsis = k_set.sorted_by_attribute('mass')[0].copy() #Child2 is Always Lightest + # Add in Leaves + if bigbro not in hierarchical_set: + temp = Particle() + temp.id = bigbro.id + temp.child1 = None + temp.child2 = None + temp.is_binary = False + temp.mass = bigbro.mass + temp.radius = get_physical_radius(bigbro) + hierarchical_set.add_particle(temp) # Child1 is at -2 + #elif bigbro in hierarchical_set: + # hierarchical_set[] + if lilsis not in hierarchical_set: + temp = Particle() + temp.id = lilsis.id + temp.child1 = None + temp.child2 = None + temp.is_binary = False + temp.mass = lilsis.mass + temp.radius = get_physical_radius(lilsis) + hierarchical_set.add_particle(temp) # Child2 is at -1 + # Reset bigbro and lilsis to the copies in the set + i1 = np.where(hierarchical_set.id==bigbro.id)[0][0] + i2 = np.where(hierarchical_set.id==lilsis.id)[0][0] + bigbro = hierarchical_set[i1] + lilsis = hierarchical_set[i2] + # Add in Root + root_particle = Particle() + root_particle.mass = bigbro.mass+lilsis.mass + root_particle.is_binary = True + root_particle.semimajor_axis = semimajor_axis + root_particle.eccentricity = eccentricity + root_particle.inclination = inclination + root_particle.argument_of_pericenter = arg_per + root_particle.longitude_of_ascending_node = long_asc_node + root_particle.period = 2.0*np.pi/np.sqrt(constants.G*(bigbro.mass))*semimajor_axis**(3./2.) + root_particle.child1 = bigbro + root_particle.child2 = lilsis + root_particle.id = root_particle.child1.id+root_particle.child2.id + hierarchical_set.add_particle(root_particle) + break + continue + #for particle in hierarchical_set: + # if particle.is_binary == True: + # print(particle.child1.id, particle.child2.id) + # else: + # print(particle.id) + return hierarchical_set.copy() + +def map_node_oe_to_lilsis(hierarchical_set): + for node in hierarchical_set.select(lambda x : x == True, ["is_binary"]): + lilsis = node.child2 + lilsis.semimajor_axis = node.semimajor_axis + lilsis.eccentricity = node.eccentricity + lilsis.inclination = node.inclination + lilsis.argument_of_pericenter = node.argument_of_pericenter + lilsis.longitude_of_ascending_node = node.longitude_of_ascending_node + lilsis.period = node.period + +def run_secularmultiple(particle_set, end_time, N_output=100, debug_mode=True, genT4System=True, exportData=True): + py_particles = get_full_hierarchical_structure(particle_set) + nodes = py_particles.select(lambda x : x == True, ["is_binary"]) + Num_nodes = len(nodes) + + if exportData: + plot_a_AU = [[] for x in range(Num_nodes)] + plot_e = [[] for x in range(Num_nodes)] + plot_peri_AU = [[] for x in range(Num_nodes)] + plot_stellar_inc_deg = [[] for x in range(Num_nodes)] + plot_times_Myr = [] + + if genT4System: + nodes = py_particles.select(lambda x : x == True, ["is_binary"]) + print(nodes.inclination) + nodes.inclination = [-18.137, 0.0, 23.570] | units.deg + print(nodes.inclination) + + if debug_mode: + nodes = py_particles.select(lambda x : x == True, ["is_binary"]) + print('='*50) + print('t/kyr',0.00) + print('a/AU', nodes.semimajor_axis) + print('p/day', nodes.period) + print('e',nodes.eccentricity) + print('i/deg', nodes.inclination) + print('AP/deg', \ + nodes.argument_of_pericenter) + print('LAN/deg', \ + nodes.longitude_of_ascending_node) + + code = SecularMultiple() + code.particles.add_particles(py_particles) + + channel_from_particles_to_code = py_particles.new_channel_to(code.particles) + channel_from_code_to_particles = code.particles.new_channel_to(py_particles) + channel_from_particles_to_code.copy() + + time = 0.0|units.yr + output_time_step = end_time/float(N_output) + + if exportData: + plot_times_Myr.append(time.value_in(units.Myr)) + for i, node in enumerate(nodes): + plot_a_AU[i].append(node.semimajor_axis.value_in(units.AU)) + plot_e[i].append(node.eccentricity) + plot_peri_AU[i].append(node.semimajor_axis.value_in(units.AU)*(1.0-node.eccentricity)) + plot_stellar_inc_deg[i].append(node.inclination.value_in(units.deg)) + + while time <= end_time: + time += output_time_step + code.evolve_model(time) + print('Evolved model to:', time.value_in(units.Myr), "Myr") + channel_from_code_to_particles.copy() + + if exportData: + plot_times_Myr.append(time.value_in(units.Myr)) + for i, node in enumerate(nodes): + plot_a_AU[i].append(node.semimajor_axis.value_in(units.AU)) + plot_e[i].append(node.eccentricity) + plot_peri_AU[i].append(node.semimajor_axis.value_in(units.AU)*(1.0-node.eccentricity)) + plot_stellar_inc_deg[i].append(node.inclination.value_in(units.deg)) + + if time == end_time+output_time_step: + map_node_oe_to_lilsis(py_particles) + + if debug_mode: + if time == end_time or time==output_time_step: + print('='*50) + print('t/kyr',time.value_in(units.kyr)) + print('a/AU', nodes.semimajor_axis) + print('p/day', nodes.period) + print('e',nodes.eccentricity) + print('i/deg', nodes.inclination) + print('AP/deg', \ + nodes.argument_of_pericenter) + print('LAN/deg', \ + nodes.longitude_of_ascending_node) + code.stop() + if exportData: + data = plot_times_Myr,plot_a_AU, plot_e, plot_peri_AU, plot_stellar_inc_deg + return py_particles, data + else: + return py_particles From f542131012c20ed14fd0a6da841d74a4d4111d54 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Fri, 3 Jul 2020 10:29:36 -0400 Subject: [PATCH 002/130] Changing debug options to False by default on run_secularmultiple --- src/tycho/enc_patching.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index fd787fe..b62a00e 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -148,7 +148,7 @@ def map_node_oe_to_lilsis(hierarchical_set): lilsis.longitude_of_ascending_node = node.longitude_of_ascending_node lilsis.period = node.period -def run_secularmultiple(particle_set, end_time, N_output=100, debug_mode=True, genT4System=True, exportData=True): +def run_secularmultiple(particle_set, end_time, N_output=100, debug_mode=False, genT4System=False, exportData=False): py_particles = get_full_hierarchical_structure(particle_set) nodes = py_particles.select(lambda x : x == True, ["is_binary"]) Num_nodes = len(nodes) From 08b7de1d019f113fc71398a5f29e2cd8dadfba69 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 14 Jul 2020 23:20:33 -0400 Subject: [PATCH 003/130] Changes to Secular Patching --- sim_encounters.py | 20 +++++++++++++++++++- src/tycho/enc_patching.py | 20 ++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/sim_encounters.py b/sim_encounters.py index d54c7e6..d388bc7 100644 --- a/sim_encounters.py +++ b/sim_encounters.py @@ -39,7 +39,7 @@ from amuse.community.ph4.interface import ph4 # Import the Tycho Packages -from tycho import create, util, read, write, stellar_systems +from tycho import create, util, read, write, stellar_systems, enc_patching # Set Backend (For DRACO Only) import matplotlib; matplotlib.use('agg') @@ -225,6 +225,24 @@ def run_collision(bodies, end_time, delta_time, save_file, **kwargs): # Seperate out the Systems to Prepare for Encounter Patching if doEncPatching: ResultingPSystems = stellar_systems.get_heirarchical_systems_from_set(GravitatingBodies, converter=converter, RelativePosition=True) + # Grab the System Around the Encounter Key's Star + KeySystem = + + # Set Simulation Conditions for Secular Multiple + shortest_orbit_period = np.min(KeySystem.period) + maximum_AMDBeta = np.max(KeyStsem.AMDBeta) + time_till_next_encounter = + + if maximum_AMDBeta < 1.0: + t_end = 10*shortest_orbital_period + elif time_till_next_encounter == 0.0 | units.Myr: + t_end = # Time to 2 Gyr + else: + t_end = time_till_next_encounter + + # Run Secular Multiple + enc_patching.run_secularmultiple(KeySystem, t_end, ) + else: ResultingPSystems = GravitatingBodies return ResultingPSystems diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index b62a00e..8bed578 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -9,6 +9,7 @@ from amuse.community.secularmultiple.interface import SecularMultiple from amuse.datamodel.trees import BinaryTreesOnAParticleSet +from amuse.ext.orbital_elements import get_orbital_elements_from_binary set_printing_strategy("custom", preferred_units = [units.MSun, units.AU, units.day, units.deg], precision = 6, prefix = "", separator = "[", suffix = "]") @@ -35,7 +36,7 @@ def get_root_of_leaf(particle_set, chosen_id): if chosen_id in leaves_id: return root.particle - from amuse.ext.orbital_elements import get_orbital_elements_from_binary + def get_physical_radius(particle): try: @@ -98,6 +99,9 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): temp.is_binary = False temp.mass = bigbro.mass temp.radius = get_physical_radius(bigbro) + temp.position = bigbro.position + temp.velocity = bigbro.velocity + print(bigbro.velocity) hierarchical_set.add_particle(temp) # Child1 is at -2 #elif bigbro in hierarchical_set: # hierarchical_set[] @@ -109,6 +113,9 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): temp.is_binary = False temp.mass = lilsis.mass temp.radius = get_physical_radius(lilsis) + temp.position = lilsis.position + temp.velocity = lilsis.velocity + print(lilsis.velocity) hierarchical_set.add_particle(temp) # Child2 is at -1 # Reset bigbro and lilsis to the copies in the set i1 = np.where(hierarchical_set.id==bigbro.id)[0][0] @@ -127,6 +134,8 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): root_particle.period = 2.0*np.pi/np.sqrt(constants.G*(bigbro.mass))*semimajor_axis**(3./2.) root_particle.child1 = bigbro root_particle.child2 = lilsis + root_particle.position = (hierarchical_set.select(lambda x : x == False, ["is_binary"])).center_of_mass() + root_particle.velocity = (hierarchical_set.select(lambda x : x == False, ["is_binary"])).center_of_mass_velocity() root_particle.id = root_particle.child1.id+root_particle.child2.id hierarchical_set.add_particle(root_particle) break @@ -149,7 +158,14 @@ def map_node_oe_to_lilsis(hierarchical_set): lilsis.period = node.period def run_secularmultiple(particle_set, end_time, N_output=100, debug_mode=False, genT4System=False, exportData=False): - py_particles = get_full_hierarchical_structure(particle_set) + try: + hierarchical_test = [x for x in particle_set if x.is_binary == True] + print("The supplied set has", len(hierarchical_test), "node particles and is a tree.") + py_particles = particle_set.copy() + except: + print("The supplied set is NOT a tree set! Building tree ...") + py_particles = get_full_hierarchical_structure(particle_set) + print("Tree has been built with", len(hierarchical_test), "node particles.") nodes = py_particles.select(lambda x : x == True, ["is_binary"]) Num_nodes = len(nodes) From aa7982a2ccd06d80dccd3ff9c32828f1ddc7467b Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 18 Jul 2020 12:44:06 -0400 Subject: [PATCH 004/130] Update to sim_cluster using new function update_leaves_pos_vel. --- sim_cluster.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sim_cluster.py b/sim_cluster.py index 0ceb12a..e17b675 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -534,13 +534,16 @@ def move_particle(set_from, set_to, particle_id): # Evolve the Gravitational Codes ( via Bridge Code) bridge_code.evolve_model(t_current) + # Update the Leaves of the Multiples Code Particles + multiples_code.update_leaves_pos_vel() + # Sync the Gravitational Codes w/ the "Gravitating_Bodies" Superset channel_from_multi_to_gravitating.copy_attributes(['x', 'y', 'z', 'vx', 'vy', 'vz']) # (On a Copy) Recursively Expand All Top-Level Parent Particles & Update Subsets # Note: This Updates the Children's Positions Relative to their Top-Level Parent's Position - subset_sync = ChildUpdater() - subset_sync.update_children_bodies(multiples_code, Individual_Stars, Planets) + #subset_sync = ChildUpdater() + #subset_sync.update_children_bodies(multiples_code, Individual_Stars, Planets) # Evolve the Stellar Codes (via SEV Code with Channels) # TODO: Ensure Binaries are Evolved Correctly (See Section 3.2.8) From c49da689586b498a9204e3fcf185215bd5c9c970 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 18 Jul 2020 13:17:17 -0400 Subject: [PATCH 005/130] Reduced Max Period to ~0.1 Myr for Binaries. This should reduce enc veto --- src/tycho/create.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/create.py b/src/tycho/create.py index b401fa3..1caeb29 100644 --- a/src/tycho/create.py +++ b/src/tycho/create.py @@ -184,7 +184,7 @@ def binary_system_v2(star_to_become_binary, **kwargs): doSana_P = kwargs.get("SanaP", False) # Apply Sana et al. (2012) Period Distribution Pcirc = kwargs.get("Pcirc", 6 | units.day ) # Circularization Period Pmin = kwargs.get("Pmin", 10.**-1. | units.day ) # Min Orbital Period Allowed - Pmax = kwargs.get("Pmax", 10.**10. | units.day ) # Max Orbital Period Allowed + Pmax = kwargs.get("Pmax", 10.**7. | units.day ) # Max Orbital Period Allowed kepler_worker = kwargs.get("kepler_worker", None) # Define Original Star's Information From d23ed196487ca5fb188e5310376812f118956038 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 28 Jul 2020 13:02:07 -0400 Subject: [PATCH 006/130] Updates to Binary Creation via setting maximum Period based on perturber --- sim_encounters.py | 18 +----------------- src/tycho/create.py | 44 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/sim_encounters.py b/sim_encounters.py index d388bc7..6fd4b57 100644 --- a/sim_encounters.py +++ b/sim_encounters.py @@ -224,24 +224,8 @@ def run_collision(bodies, end_time, delta_time, save_file, **kwargs): over_grav.reset() # Seperate out the Systems to Prepare for Encounter Patching if doEncPatching: - ResultingPSystems = stellar_systems.get_heirarchical_systems_from_set(GravitatingBodies, converter=converter, RelativePosition=True) - # Grab the System Around the Encounter Key's Star - KeySystem = - # Set Simulation Conditions for Secular Multiple - shortest_orbit_period = np.min(KeySystem.period) - maximum_AMDBeta = np.max(KeyStsem.AMDBeta) - time_till_next_encounter = - - if maximum_AMDBeta < 1.0: - t_end = 10*shortest_orbital_period - elif time_till_next_encounter == 0.0 | units.Myr: - t_end = # Time to 2 Gyr - else: - t_end = time_till_next_encounter - - # Run Secular Multiple - enc_patching.run_secularmultiple(KeySystem, t_end, ) + enc_patching.doEncPatching(GravitatingBodies) else: ResultingPSystems = GravitatingBodies diff --git a/src/tycho/create.py b/src/tycho/create.py index 1caeb29..e4c89e5 100644 --- a/src/tycho/create.py +++ b/src/tycho/create.py @@ -117,7 +117,7 @@ def king_cluster_v2(num_stars, **kwargs): # Split the Binary into its Companions & Store in Seperate Sets for com_index in ids_to_become_binaries: stars_SI[com_index].id = com_index - binary_particle, singles_in_binary = binary_system_v2(stars_SI[com_index], kepler_worker=kep) + binary_particle, singles_in_binary = binary_system_v2(stars_SI[com_index], stars_SI, kepler_worker=kep) binaries.add_particle(binary_particle) singles_in_binaries.add_particle(singles_in_binary) com_to_remove.add_particle(stars_SI[com_index]) @@ -175,7 +175,7 @@ def find_possible_binaries_v2(com_mass_array, **kwargs): current_com_id += 1 return com_mass_array, ids_to_become_binaries -def binary_system_v2(star_to_become_binary, **kwargs): +def binary_system_v2(star_to_become_binary, set_of_stars, **kwargs): # Check Keyword Arguments doFlatEcc = kwargs.get("FlatEcc",True) # Apply Uniform Eccentricity Distribution doBasic = kwargs.get("Basic", False) # Apply a Basic Binary Distribution @@ -214,6 +214,15 @@ def binary_system_v2(star_to_become_binary, **kwargs): star1.mass = star_to_become_binary.mass / (1. + q) star2.mass = q * star1.mass +# If Desired, Apply Uniform Eccentricity Distribution + if (doFlatEcc): + e = rp.uniform(0.0,1.0) + +# Set the Maximum Period Allowed by Perturbers + Pmax_by_perturber = set_max_period_from_perturber(star_to_become_binary, set_of_stars, eccentricity= e) + if Pmax_by_perturber < Pmax: + Pmax = Pmax_by_perturber + # If Desired, Apply Raghavan et al. (2010) Period Distribution if (doRag_P): sigma = 2.28 @@ -225,7 +234,6 @@ def binary_system_v2(star_to_become_binary, **kwargs): period = 10.**logP | units.day semimajor_axis = ((period**2.)/(4.*np.pi**2.)*constants.G*(star1.mass+star2.mass))**(1./3.) - # If Desired & Applicable, Apply Sana et al. (2012) Period Distribution if (doSana_P and star1.mass > 15 | units.MSun): maxLogP = np.log10(Pmax.value_in(units.day)) @@ -236,9 +244,7 @@ def binary_system_v2(star_to_become_binary, **kwargs): period = 10.**logP | units.day semimajor_axis = ((period**2.)/(4.*np.pi**2.)*constants.G*(star1.mass+star2.mass))**(1./3.) -# If Desired, Apply Uniform Eccentricity Distribution - if (doFlatEcc): - e = rp.uniform(0.0,1.0) +# Always circularize low period Binaries if (period < Pcirc): e = 0.0 @@ -399,3 +405,29 @@ def planet_v2(ID, host_star, planet_mass, init_a, init_e, random_orientation=Fal p.velocity = vCM + newPSystem[1].velocity # Returns the Created AMUSE Particle return p + +def set_max_period_from_perturber(center_of_mass, particle_set, **kwargs): + perturb_limit = kwargs.get("perturbation_limit", 0.02) + e = kwargs.get("eccentricity", 0.0) + verbose = kwargs.get("verbose", False) + + # Calculate Nearest Neighbors + other_stars = particle_set - center_of_mass + dist_vect = other_stars.position - center_of_mass.position + distances = dist_vect.lengths() + + # Calculate Perterbation of Nearest Neighbors on System + pert = other_stars.mass / distances**3 + primary_pert_index = np.where(pert == max(pert))[0] + primary_perturber = other_stars[primary_pert_index][0] + perturb_distance = distances[primary_pert_index][0] + perturb_mass = primary_perturber.mass + + # Calculate Maximum Period + P_max_Squared = (4*np.power(np.pi, 2)*(perturb_distance**3)*perturb_limit)/(units.constants.G*((1+e)**3)*perturb_mass) + P_max = np.sqrt(P_max_Squared)[0] + + if verbose: + print("Limiting Maximum Period to", P_max, "in accordance to the largest perturber at index", primary_pert_index) + print("Distance to Perturber:", perturb_distance, " | Mass of Perturber:", perturb_mass) + return P_max From 9ee5b467ba81be3e3c745a8cbfcbb175a8b6e7ff Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 28 Jul 2020 13:03:34 -0400 Subject: [PATCH 007/130] Changing Dynamic Radius for Singles to be apoapsis. --- src/tycho/create.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/create.py b/src/tycho/create.py index e4c89e5..9b5ba17 100644 --- a/src/tycho/create.py +++ b/src/tycho/create.py @@ -260,7 +260,7 @@ def binary_system_v2(star_to_become_binary, set_of_stars, **kwargs): star2.velocity = vCM + newBinary[1].velocity # Apply a Fitting Dynamical Radius - singles_in_binary.radius = 2*semimajor_axis + singles_in_binary.radius = semimajor_axis*(1+e) # Ensure Binary Components are Approaching Each Other From 623afbad66a41ac4f421313c08209877a1d92845 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 15:02:08 -0400 Subject: [PATCH 008/130] Possible error in radius approx. --- src/tycho/enc_patching.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 8bed578..81190f4 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -46,8 +46,10 @@ def get_physical_radius(particle): print('No physical Radius Given, Applying Estimate!') if particle.type == 'planet': return 1 | units.RJupiter - if particle.type == 'star': + elif particle.type == 'star': return 1 | units.RSun + elif particle.is_binary == True: + return 0 | units.RSun def get_full_hierarchical_structure(bodies, RelativePosition=False): From fe8b0a624488564f7a7856e391f8c4c2162236c7 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 16:26:12 -0400 Subject: [PATCH 009/130] Fixes to physical radius estimates. --- src/tycho/enc_patching.py | 13 +++++++++---- src/tycho/util.py | 9 +++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 81190f4..777e291 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -44,10 +44,15 @@ def get_physical_radius(particle): return radius except: print('No physical Radius Given, Applying Estimate!') - if particle.type == 'planet': - return 1 | units.RJupiter - elif particle.type == 'star': - return 1 | units.RSun + if particle.mass <= 13 | units.MJupiter: + if particle.mass == 0.003 | units.MJupiter: + return 1 | units.REarth + elif particle.mass == 1 | units.MJupiter: + return 1 | units.RJupiter + elif particle.mass == 0.054 | units.MJupiter: + return 4 | units.REarth + else: + return util.get_stellar_radius(particle) elif particle.is_binary == True: return 0 | units.RSun diff --git a/src/tycho/util.py b/src/tycho/util.py index 484fa68..0ebfe0f 100644 --- a/src/tycho/util.py +++ b/src/tycho/util.py @@ -260,3 +260,12 @@ def ensure_approaching_binary(primary, secondary, kepler_worker=None): if kepler_worker == None: kep.stop() return primary, secondary + + +def get_stellar_radius(star): + sev_code = SSE() + sev_code.particles.add_particle(star) + channel_from_sev_to_code = sev_code.particles.new_channel_to(star) + sev_code.model_time = host_star.time + sev_code.evolve_model(host_star.time) + return sev_code.particles.star.radius From 3e78fd4e2fd4d3bca94cdeb658e106039f3902d1 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 16:28:25 -0400 Subject: [PATCH 010/130] Fixed syntax. --- src/tycho/enc_patching.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 777e291..8c76b81 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -53,8 +53,7 @@ def get_physical_radius(particle): return 4 | units.REarth else: return util.get_stellar_radius(particle) - elif particle.is_binary == True: - return 0 | units.RSun + def get_full_hierarchical_structure(bodies, RelativePosition=False): From e60709779102eb510c93b03c52a39ed542f18b77 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 16:33:01 -0400 Subject: [PATCH 011/130] Attempt to fix physical_radius() --- src/tycho/util.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tycho/util.py b/src/tycho/util.py index 0ebfe0f..83402e9 100644 --- a/src/tycho/util.py +++ b/src/tycho/util.py @@ -264,8 +264,10 @@ def ensure_approaching_binary(primary, secondary, kepler_worker=None): def get_stellar_radius(star): sev_code = SSE() - sev_code.particles.add_particle(star) - channel_from_sev_to_code = sev_code.particles.new_channel_to(star) + temp_star = Particle() + temp_star.mass = star._mass + temp_star.age = star.time + sev_code.particles.add_particle(temp_star) sev_code.model_time = host_star.time sev_code.evolve_model(host_star.time) return sev_code.particles.star.radius From a928bbfb78cf05a1b3d089fffd7ed1c55cd302b4 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 16:34:15 -0400 Subject: [PATCH 012/130] Syntax error. --- src/tycho/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/util.py b/src/tycho/util.py index 83402e9..624724f 100644 --- a/src/tycho/util.py +++ b/src/tycho/util.py @@ -265,7 +265,7 @@ def ensure_approaching_binary(primary, secondary, kepler_worker=None): def get_stellar_radius(star): sev_code = SSE() temp_star = Particle() - temp_star.mass = star._mass + temp_star.mass = star.mass temp_star.age = star.time sev_code.particles.add_particle(temp_star) sev_code.model_time = host_star.time From b3694f2d786fbecb46fc9b66849b77655784974a Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 16:35:29 -0400 Subject: [PATCH 013/130] Another syntax fix. --- src/tycho/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tycho/util.py b/src/tycho/util.py index 624724f..b3cc18f 100644 --- a/src/tycho/util.py +++ b/src/tycho/util.py @@ -268,6 +268,6 @@ def get_stellar_radius(star): temp_star.mass = star.mass temp_star.age = star.time sev_code.particles.add_particle(temp_star) - sev_code.model_time = host_star.time - sev_code.evolve_model(host_star.time) + sev_code.model_time = star.time + sev_code.evolve_model(star.time) return sev_code.particles.star.radius From 4b7d137758036a65a3cc43abe2ae76e7d1e7ed5e Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 16:36:00 -0400 Subject: [PATCH 014/130] Yet another fix. --- src/tycho/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/util.py b/src/tycho/util.py index b3cc18f..c056341 100644 --- a/src/tycho/util.py +++ b/src/tycho/util.py @@ -270,4 +270,4 @@ def get_stellar_radius(star): sev_code.particles.add_particle(temp_star) sev_code.model_time = star.time sev_code.evolve_model(star.time) - return sev_code.particles.star.radius + return sev_code.particles.radius[0] From e8e48e0f97414efc3ae4a89d85dcca40baba691c Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 16:38:20 -0400 Subject: [PATCH 015/130] Fix? --- src/tycho/util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tycho/util.py b/src/tycho/util.py index c056341..2dbd9de 100644 --- a/src/tycho/util.py +++ b/src/tycho/util.py @@ -270,4 +270,5 @@ def get_stellar_radius(star): sev_code.particles.add_particle(temp_star) sev_code.model_time = star.time sev_code.evolve_model(star.time) - return sev_code.particles.radius[0] + print(sev_code.particles[0].radius) + return sev_code.particles[0].radius From 91f7fa604cc495df42c34ce17a6c06be1000ac68 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 16:46:23 -0400 Subject: [PATCH 016/130] Possible fix. --- src/tycho/enc_patching.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 8c76b81..1d8b1c5 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -58,6 +58,8 @@ def get_physical_radius(particle): def get_full_hierarchical_structure(bodies, RelativePosition=False): hierarchical_set = Particles() + for body in bodies: + body.radius = get_physical_radius(body) # Calculate Distances to All Bodies for Each Body for index, body in enumerate(bodies): try: @@ -104,7 +106,7 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): temp.child2 = None temp.is_binary = False temp.mass = bigbro.mass - temp.radius = get_physical_radius(bigbro) + temp.radius = bigbro.radius temp.position = bigbro.position temp.velocity = bigbro.velocity print(bigbro.velocity) @@ -118,7 +120,7 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): temp.child2 = None temp.is_binary = False temp.mass = lilsis.mass - temp.radius = get_physical_radius(lilsis) + temp.radius = lilsis.radius temp.position = lilsis.position temp.velocity = lilsis.velocity print(lilsis.velocity) @@ -133,6 +135,7 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): root_particle.mass = bigbro.mass+lilsis.mass root_particle.is_binary = True root_particle.semimajor_axis = semimajor_axis + root_particle.radius = 0 | units.RSun root_particle.eccentricity = eccentricity root_particle.inclination = inclination root_particle.argument_of_pericenter = arg_per From d27108b3826f49d0064a73dd27675b2f2da69401 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 16:50:20 -0400 Subject: [PATCH 017/130] Fix? --- src/tycho/enc_patching.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 1d8b1c5..792df20 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -59,7 +59,9 @@ def get_physical_radius(particle): def get_full_hierarchical_structure(bodies, RelativePosition=False): hierarchical_set = Particles() for body in bodies: + print(body.id) body.radius = get_physical_radius(body) + print(body.radius) # Calculate Distances to All Bodies for Each Body for index, body in enumerate(bodies): try: From 433ccbf20ec0dc5e162978e7a03dcc3fd8d225e5 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 16:55:46 -0400 Subject: [PATCH 018/130] Possible fix. --- src/tycho/enc_patching.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 792df20..770df70 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -45,12 +45,12 @@ def get_physical_radius(particle): except: print('No physical Radius Given, Applying Estimate!') if particle.mass <= 13 | units.MJupiter: - if particle.mass == 0.003 | units.MJupiter: + if particle.mass <= 0.003 | units.MJupiter: return 1 | units.REarth - elif particle.mass == 1 | units.MJupiter: - return 1 | units.RJupiter - elif particle.mass == 0.054 | units.MJupiter: + elif particle.mass <= 0.054 | units.MJupiter: return 4 | units.REarth + elif particle.mass <= 1 | units.MJupiter: + return 1 | units.RJupiter else: return util.get_stellar_radius(particle) From 92b85bf75f204be16b1d38f67739e89fbe470d1c Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 16:59:55 -0400 Subject: [PATCH 019/130] One more time! --- src/tycho/enc_patching.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 770df70..9ba7f99 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -45,9 +45,9 @@ def get_physical_radius(particle): except: print('No physical Radius Given, Applying Estimate!') if particle.mass <= 13 | units.MJupiter: - if particle.mass <= 0.003 | units.MJupiter: + if particle.mass <= 0.01 | units.MJupiter: return 1 | units.REarth - elif particle.mass <= 0.054 | units.MJupiter: + elif particle.mass <= 0.1 | units.MJupiter: return 4 | units.REarth elif particle.mass <= 1 | units.MJupiter: return 1 | units.RJupiter From f9910329910bcbba7931ca86f99de5228604b277 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 17:04:49 -0400 Subject: [PATCH 020/130] Syntax fix. --- src/tycho/enc_patching.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 9ba7f99..75bdd26 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -176,6 +176,7 @@ def run_secularmultiple(particle_set, end_time, N_output=100, debug_mode=False, except: print("The supplied set is NOT a tree set! Building tree ...") py_particles = get_full_hierarchical_structure(particle_set) + hierarchical_test = [x for x in particle_set if x.is_binary == True] print("Tree has been built with", len(hierarchical_test), "node particles.") nodes = py_particles.select(lambda x : x == True, ["is_binary"]) Num_nodes = len(nodes) From 0caf3961dc27c3c5513e5f200785ad85c1466c52 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 17:09:24 -0400 Subject: [PATCH 021/130] Fixes! --- src/tycho/enc_patching.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 75bdd26..1743e57 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -172,11 +172,11 @@ def run_secularmultiple(particle_set, end_time, N_output=100, debug_mode=False, try: hierarchical_test = [x for x in particle_set if x.is_binary == True] print("The supplied set has", len(hierarchical_test), "node particles and is a tree.") - py_particles = particle_set.copy() + py_particles = particle_set except: print("The supplied set is NOT a tree set! Building tree ...") py_particles = get_full_hierarchical_structure(particle_set) - hierarchical_test = [x for x in particle_set if x.is_binary == True] + hierarchical_test = [x for x in py_particles if x.is_binary == True] print("Tree has been built with", len(hierarchical_test), "node particles.") nodes = py_particles.select(lambda x : x == True, ["is_binary"]) Num_nodes = len(nodes) From ef4701f874501adb593d7d5f8bd3d205b83e55e7 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 17:33:48 -0400 Subject: [PATCH 022/130] Fix to start of the secular model. --- src/tycho/enc_patching.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 1743e57..d0080f0 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -168,7 +168,7 @@ def map_node_oe_to_lilsis(hierarchical_set): lilsis.longitude_of_ascending_node = node.longitude_of_ascending_node lilsis.period = node.period -def run_secularmultiple(particle_set, end_time, N_output=100, debug_mode=False, genT4System=False, exportData=False): +def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), N_output=100, debug_mode=False, genT4System=False, exportData=False): try: hierarchical_test = [x for x in particle_set if x.is_binary == True] print("The supplied set has", len(hierarchical_test), "node particles and is a tree.") @@ -209,12 +209,13 @@ def run_secularmultiple(particle_set, end_time, N_output=100, debug_mode=False, code = SecularMultiple() code.particles.add_particles(py_particles) + code.model_time(start_time) channel_from_particles_to_code = py_particles.new_channel_to(code.particles) channel_from_code_to_particles = code.particles.new_channel_to(py_particles) channel_from_particles_to_code.copy() - time = 0.0|units.yr + time = start_time output_time_step = end_time/float(N_output) if exportData: From 903ba35e95284442d3673eea39e0bc5ce807d143 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 1 Aug 2020 20:07:05 -0400 Subject: [PATCH 023/130] Fixing start time. --- src/tycho/enc_patching.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index d0080f0..6810e32 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -209,7 +209,7 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), N_out code = SecularMultiple() code.particles.add_particles(py_particles) - code.model_time(start_time) + code.model_time = start_time channel_from_particles_to_code = py_particles.new_channel_to(code.particles) channel_from_code_to_particles = code.particles.new_channel_to(py_particles) From 9abe5d400358cc154d99c5cddfc606fb876a5c9a Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 8 Aug 2020 15:03:15 -0400 Subject: [PATCH 024/130] Preparing for Bulk Scattering Update --- src/tycho/enc_patching.py | 27 +++- src/tycho/scattering.py | 302 ++++++++++++++++++++++++++++++++++++++ src/tycho/util.py | 4 +- 3 files changed, 329 insertions(+), 4 deletions(-) create mode 100644 src/tycho/scattering.py diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 6810e32..c0f670c 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -168,7 +168,20 @@ def map_node_oe_to_lilsis(hierarchical_set): lilsis.longitude_of_ascending_node = node.longitude_of_ascending_node lilsis.period = node.period -def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), N_output=100, debug_mode=False, genT4System=False, exportData=False): +def reset_secularmultiples(code): + unit_e = unit_m*unit_l**2/(unit_t**2) + code.particles = Particles() + code.model_time = 0.0 | units.Myr + code.particles_committed = False + code.initial_hamiltonian = 0.0 | unit_e + code.hamiltonian = 0.0 | unit_e + code.flag = 0 + code.error_code = 0 + return code + +def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ + N_output=100, debug_mode=False, genT4System=False, \ + exportData=False, GCode = None): try: hierarchical_test = [x for x in particle_set if x.is_binary == True] print("The supplied set has", len(hierarchical_test), "node particles and is a tree.") @@ -181,6 +194,11 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), N_out nodes = py_particles.select(lambda x : x == True, ["is_binary"]) Num_nodes = len(nodes) + if GCode == None: + code = SecularMultiple() + else: + code = GCode + if exportData: plot_a_AU = [[] for x in range(Num_nodes)] plot_e = [[] for x in range(Num_nodes)] @@ -207,8 +225,8 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), N_out print('LAN/deg', \ nodes.longitude_of_ascending_node) - code = SecularMultiple() code.particles.add_particles(py_particles) + code.commit_particles() code.model_time = start_time channel_from_particles_to_code = py_particles.new_channel_to(code.particles) @@ -255,7 +273,10 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), N_out nodes.argument_of_pericenter) print('LAN/deg', \ nodes.longitude_of_ascending_node) - code.stop() + if GCode == None: + code.stop() + else: + code = reset_secularmultiples(code) if exportData: data = plot_times_Myr,plot_a_AU, plot_e, plot_peri_AU, plot_stellar_inc_deg return py_particles, data diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py new file mode 100644 index 0000000..73333fc --- /dev/null +++ b/src/tycho/scattering.py @@ -0,0 +1,302 @@ +from tycho import * +from tycho import stellar_systems +from tycho import enc_patching +from amuse import * +import numpy as np +import matplotlib.pyplot as plt +from amuse.units import units + +import glob + +import hashlib + +import pickle + +from collections import defaultdict + +from amuse.community.secularmultiple.interface import SecularMultiple +from amuse.datamodel.trees import BinaryTreesOnAParticleSet + +set_printing_strategy("custom", preferred_units = [units.MSun, units.AU, units.day, units.deg], precision = 6, prefix = "", separator = "[", suffix = "]") + +def build_ClusterEncounterHistory(rootExecDir): + # Strip off Extra '/' if added by user to bring inline with os.cwd() + if rootExecDir.endswith("/"): + rootExecDir = rootExecDir[:-1] + # Define the Cluster's Name + #cluster_name = rootExecDir.split("/")[-1] + # Generate List of Scattering IC HDF5 Paths + paths_of_IC_files = glob.glob(rootExecDir+'/Scatter_IC/*/*.hdf5') + # Find all Primary Star IDs + star_IDs = np.unique([int(path.split("/")[-2]) for path in paths_of_IC_files]) # '1221' + EncounterHistory = defaultdict(dict) + for i, star_ID in enumerate(star_IDs[:10]): + RotationKeys = np.unique([path.split("/")[-1].split(".")[-2].split('_')[-1] for path in paths_of_IC_files if int(path.split("/")[-2]) == star_ID]) + EncounterKeys = np.unique([path.split("/")[-1].split(".")[-2].split('_')[0] for path in paths_of_IC_files if int(path.split("/")[-2]) == star_ID]) + EncounterHistory[star_ID] = defaultdict(dict) + for RotationKey in RotationKeys: + EncounterHistory[star_ID][RotationKey] = [] + for EncounterKey in EncounterKeys: + EncounterHistory[star_ID][RotationKey].append(str(rootExecDir+'/Scatter_IC/'+str(star_ID)+'/'+EncounterKey+'_'+RotationKey+'.hdf5')) + return EncounterHistory + + +class CloseEncounters(): + def __init__(self, Star_EncounterHistory, KeplerWorkerList = None): + '''EncounterHistory should be a List of the Format {RotationKey: [Encounter0_FilePath, ...]}''' + # Find the Main System's Host Star's ID and Assign it to 'KeySystemID' + self.doEncounterPatching = True + self.KeySystemID = int(Star_EncounterHistory[list(Star_EncounterHistory)[0]][0].split("/")[-2]) + self.ICs = defaultdict(list) + self.StartTimes = defaultdict(list) + self.desired_endtime = 2.0 | units.Gyr + self.max_end_time = 0.1 | units.Myr + self.kep = KeplerWorkerList + # Create a List of StartingTimes and Encounter Initial Conditions (ICs) for all Orientations + for RotationKey in Star_EncounterHistory.keys(): + for i, Encounter in enumerate(Star_EncounterHistory[RotationKey]): + self.ICs[RotationKey].append(read_set_from_file(Encounter, format="hdf5", version='2.0', close_file=True)) + self.StartTimes[RotationKey].append(np.max(np.unique(self.ICs[RotationKey][i].time))) + #print(self.KeySystemID) + self.FinalStates = defaultdict(list) + + def SimAllEncounters(self): + ''' Call this function to run all Encounters for a System.''' + # Start up Kepler Functions if Needed + if self.kep == None: + self.kep = [] + bodies = self.ICs[next(iter(self.ICs))][0] + converter = nbody_system.nbody_to_si(bodies.mass.sum(), 2 * np.max(bodies.radius.number) | bodies.radius.unit) + self.kep.append(Kepler(unit_converter = converter, redirection = 'none')) + self.kep.append(Kepler(unit_converter = converter, redirection = 'none')) + self.kep[0].initialize_code() + self.kep[1].initialize_code() + # Begin Looping over Rotation Keys ... + for RotationKey in self.ICs.keys(): + for i in range(len(self.ICs[RotationKey])): + + # Identify the Current Encounter in the List for This Rotation + CurrentEncounter = self.ICs[RotationKey][i] + print(CurrentEncounter[0].position) + + # Create the Encounter Instance with the Current Encounter + Encounter_Inst = self.SingleEncounter(CurrentEncounter) + + # Simulate the Encounter till the Encounter is Over via N-Body Integrator + # -OR- the time to the Next Encounter is Reached + if len(self.StartTimes[RotationKey]) == 1: + current_max_endtime = self.max_end_time + else: + current_max_endtime = self.StartTimes[RotationKey][i+1] + EndingState = Encounter_Inst.SimSingleEncounter(current_max_endtime, \ + start_time = self.StartTimes[RotationKey][i]) + EndingStateTime = np.max(np.unique(EndingState.time)) + + print(Encounter_Inst.particles[0].position) + print("The Encounter was over after:", (EndingStateTime- self.StartTimes[RotationKey][i]).value_in(units.Myr)) + + + #print(EndingState) + #print('----------') + #print(Encounter_Inst.particles) + + # Strip off Anything Not Associated with the Key System + systems_in_current_encounter = stellar_systems.get_heirarchical_systems_from_set(EndingState, kepler_workers=self.kep) + + # Reassign the EndingState to include the Primary System ONLY + EndingState = systems_in_current_encounter[self.KeySystemID] + #print(EndingState[0].position) + + print(len(self.ICs[RotationKey])-1) + # If Encounter Patching is Desired -AND- it isn't the last Encounter + if i + 1 < len(self.ICs[RotationKey]) and self.doEncounterPatching: + + # Identify the Next Encounter in the List + NextEncounter = self.ICs[RotationKey][i+1] + + # Simulate System till the Next Encounter's Start Time + Encounter_Inst = self.SingleEncounter(EndingState) + FinalState = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], + start_time = EndingStateTime) + + # Begin Patching of the End State to the Next Encounter + self.ICs[RotationKey][i+1] = self.PatchedEncounter(FinalState, NextEncounter) + else: + # Simulate System till Desired Global Endtime + #print(CurrentEncounter[0].time.value_in(units.Myr)) + #print(EndingState[0].time.value_in(units.Myr)) + Encounter_Inst = self.SingleEncounter(EndingState) + FinalState = Encounter_Inst.SimSecularSystem(self.desired_endtime, start_time = EndingStateTime) + #print(FinalState[0].position) + + # Append the FinalState of Each Encounter to its Dictionary + self.FinalStates[RotationKey].append(FinalState) + return None + + def PatchedEncounter(self, EndingState, NextEncounter, final_time): + ''' Call this function to Patch Encounter Endstates to the Next Encounter''' + # Determine Time to Next Encounter + current_time = max(EndingState.time) + final_time = max(NextEncounter.time) + + # Map the Orbital Elements to the Child2 Particles (LilSis) [MUST BE A TREE SET] + enc_patching.map_node_oe_to_lilsis(EndingState) + + # Seperate Next Encounter Systems to Locate the Primary System + systems_at_next_encounter = stellar_systems.get_heirarchical_systems_from_set(NextEncounter) + sys_1 = systems[self.KeySystemID] + secondary_sysID = [key for key in list(systems_in_next_encounter.keys()) if key!=int(self.KeySystemID)][0] + sys_2 = systems[secondary_sysID] + + # Get Planet and Star Subsets for the Current and Next Encounter + planets_at_current_encounter = util.get_planets(EndingState) + hoststar_at_current_encounter = util.get_stars(EndingState) + planets_at_next_encounter = util.get_planets(sys_1) + hoststar_at_next_encounter = util.get_stars(sys_1) + + # Update Current Positions & Velocitys from Orbital Parameters!! + # TO-DO: Does not handle Binary Star Systems + for planet in planets_at_current_encounter: + nbody_PlanetStarPair = \ + new_binary_from_orbital_elements(hoststar_at_current_encounter.mass, planet.mass, planet.semimajor_axis, \ + eccentricity = planet.eccentricity, inclination=planet.inclination, \ + longitude_of_the_ascending_node=planet.longitude_of_the_ascending_node, \ + argument_of_periapsis=planet.argument_of_periapsis, G=units.constants.G, \ + true_anomaly = 360*rp.uniform(0.0,1.0) | units.deg) # random point in the orbit + planet.position = nbody_PlanetStarPair[1].position + planet.velocity = nbody_PlanetStarPair[1].velocity + + # Get Relative Position + Velocity of Planets at the Current Encounter + planets_at_current_encounter.position -= hoststar_at_current_encounter.position + planets_at_current_encounter.velocity -= hoststar_at_current_encounter.velocity + + # Release a Warning when Odd Planet Number Combinations Occur (Very Unlikely, More of a Safe Guard) + if len(planets_at_current_encounter) != len(planets_at_next_encounter): + print("!!!! Expected", len(planets_at_next_encounter), "planets but recieved only", len(planets_at_current_encounter)) + + # Move Planets to Host Star in the Next Encounter + for next_planet in planets_at_next_encounter: + for current_planet in planets_at_current_encounter: + if next_planet.id == current_planet.id: + next_planet.position = planets_at_current_encounter.position + hoststar_at_next_encounter.position + next_planet.velocity = planets_at_current_encounter.velocity + hoststar_at_next_encounter.velocity + break + + # Recombine Seperated Systems to Feed into SimSingleEncounter + UpdatedNextEncounter = Partciles() + UpdatedNextEncounter.add_particles(sys1) + UpdatedNextEncounter.add_particles(sys2) + + # Return the Updated and Patched Encounter as a Partcile Set for the N-Body Simulation + return UpdatedNextEncounter + + class SingleEncounter(): + def __init__(self, EncounterBodies): + self.particles = EncounterBodies + + def SimSecularSystem(self, desired_end_time, **kwargs): + start_time = kwargs.get("start_time", 0 | units.Myr) + self.particles = enc_patching.run_secularmultiple(self.particles, desired_end_time, \ + start_time = start_time, N_output=5) + return self.particles + + def SimSingleEncounter(self, max_end_time, **kwargs): + delta_time = kwargs.get("delta_time", 100 | units.yr) + converter = kwargs.get("converter", None) + start_time = kwargs.get("start_time", 0 | units.yr) + doStateSaves = kwargs.get("doStateSaves", False) + doVerbosSaves = kwargs.get("doEncPatching", False) + GCodes = kwargs.get("GCodes", None) + + GravitatingBodies = self.particles + + # Set Up the Integrators + if GCodes == None: + if converter == None: + converter = nbody_system.nbody_to_si(GravitatingBodies.mass.sum(), \ + 2 * np.max(GravitatingBodies.radius.number) | GravitatingBodies.radius.unit) + gravity = self.initialize_GravCode(ph4, converter=converter) + over_grav = self.initialize_isOverCode(converter=converter) + else: + gravity = GCodes[0] + over_grav = GCodes[1] + + # Set up SaveState if Requested + if doStateSaves: + pass # Currently Massive Memory Leak Present + + # Store Initial Center of Mass Information + rCM_i = GravitatingBodies.center_of_mass() + vCM_i = GravitatingBodies.center_of_mass_velocity() + + # Remove Attributes that Cause Issues with SmallN + if 'child1' in GravitatingBodies.get_attribute_names_defined_in_store(): + del GravitatingBodies.child1, GravitatingBodies.child2 + + # Moving the Encounter's Center of Mass to the Origin and Setting it at Rest + GravitatingBodies.position -= rCM_i + GravitatingBodies.velocity -= vCM_i + + # Add and Commit the Scattering Particles + gravity.particles.add_particles(GravitatingBodies) # adds bodies to gravity calculations + gravity.commit_particles() + #gravity.begin_time = start_time + + # Create the Channel to Python Set & Copy it Over + channel_from_grav_to_python = gravity.particles.new_channel_to(GravitatingBodies) + channel_from_grav_to_python.copy() + + # Get Free-Fall Time for the Collision + s = util.get_stars(GravitatingBodies) + t_freefall = s.dynamical_timescale() + #print(gravity.begin_time) + #print(t_freefall) + + # Setting Coarse Timesteps + list_of_times = np.arange(0.0, start_time.value_in(units.yr)+max_end_time.value_in(units.yr), \ + delta_time.value_in(units.yr)) | units.yr + stepNumber = 0 + + # Loop through the List of Coarse Timesteps + for current_time in list_of_times: + #print(current_time) + #print(GravitatingBodies.time) + #print(gravity.sync_time) + # Evolve the Model to the Desired Current Time + gravity.evolve_model(current_time) + + # Update Python Set in In-Code Set + channel_from_grav_to_python.copy() # original + channel_from_grav_to_python.copy_attribute("index_in_code", "id") + + + # Check to See if the Encounter is Over After the Freefall Time and Every 25 Steps After That + if current_time > 1.25*t_freefall and stepNumber%25 == 0: + over = util.check_isOver(gravity.particles, over_grav) + print("Is it Over?", over) + if over: + print(gravity.particles[0].position) + print(GravitatingBodies[0].position) + current_time += 100 | units.yr + # Get to a Final State After Several Planet Orbits + gravity.evolve_model(current_time) + gravity.update_particle_set() + gravity.particles.synchronize_to(GravitatingBodies) + channel_from_grav_to_python.copy() + GravitatingBodies.time = start_time + current_time + # Create a Save State + break + if current_time == list_of_times[-1]: + # Create a Save State + pass + stepNumber +=1 + if GCodes == None: + # Stop the Gravity Code Once the Encounter Finishes + gravity.stop() + over_grav.stop() + else: + gravity.reset() + over_grav.reset() + + # Return the GravitatingBodies as they are at the End of the Simulation + return GravitatingBodies diff --git a/src/tycho/util.py b/src/tycho/util.py index 2dbd9de..06c2dab 100644 --- a/src/tycho/util.py +++ b/src/tycho/util.py @@ -270,5 +270,7 @@ def get_stellar_radius(star): sev_code.particles.add_particle(temp_star) sev_code.model_time = star.time sev_code.evolve_model(star.time) + radius = sev_code.particles[0].radius print(sev_code.particles[0].radius) - return sev_code.particles[0].radius + sev_code.stop() + return radius From 3f7888ce3dfebd1a18e66fc1dcc567f89fdfa614 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sun, 9 Aug 2020 13:15:14 -0400 Subject: [PATCH 025/130] Testing fix to get physical radius. --- sim_cluster.py | 2 +- src/tycho/enc_patching.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sim_cluster.py b/sim_cluster.py index e17b675..9f055e3 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -92,7 +92,7 @@ def handle_encounter_v5(self, time, star1, star2): # Expand enconter returns a particle set with all of the children # when given a particle set of two objects involved in an encounter - enc_particles = multiples_code.expand_encounter(scattering_com.copy(), delete=False)[0] + enc_particles = multiples_code.expand_encounter(scattering_com, delete=False)[0] # Assign the time of the encounter to the Encounter Particle Set. enc_particles.time = time diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index c0f670c..52f2634 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -49,7 +49,7 @@ def get_physical_radius(particle): return 1 | units.REarth elif particle.mass <= 0.1 | units.MJupiter: return 4 | units.REarth - elif particle.mass <= 1 | units.MJupiter: + elif particle.mass <= 0.8 | units.MJupiter: return 1 | units.RJupiter else: return util.get_stellar_radius(particle) From e13c5f1d08e7d8d821a922dd6aaab4503d3f4a84 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sun, 9 Aug 2020 13:20:54 -0400 Subject: [PATCH 026/130] Testing fix to conditions for physical radius. --- src/tycho/enc_patching.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 52f2634..a00ff3b 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -49,7 +49,8 @@ def get_physical_radius(particle): return 1 | units.REarth elif particle.mass <= 0.1 | units.MJupiter: return 4 | units.REarth - elif particle.mass <= 0.8 | units.MJupiter: + else: + print(particle.id, particle.mass.value_in(units.MJupiter), "MJupiter") return 1 | units.RJupiter else: return util.get_stellar_radius(particle) From 5a91aade2ee7121d091fa4b5cb7600052bc87f48 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sun, 9 Aug 2020 13:24:44 -0400 Subject: [PATCH 027/130] Fix to resetting secularmultiples. --- src/tycho/enc_patching.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index a00ff3b..6f4de84 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -170,7 +170,10 @@ def map_node_oe_to_lilsis(hierarchical_set): lilsis.period = node.period def reset_secularmultiples(code): - unit_e = unit_m*unit_l**2/(unit_t**2) + unit_l = units.AU + unit_m = units.MSun + unit_t = 1.0e6*units.yr + unit_e = unit_m*unit_l**2/(unit_t**2) ### energy code.particles = Particles() code.model_time = 0.0 | units.Myr code.particles_committed = False From 642305d65cd8f6e9ee23bb4dca2d69d5c45be04e Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 10 Aug 2020 15:34:20 -0400 Subject: [PATCH 028/130] Major overhaul for encounter patching and scattering experiments. --- src/tycho/enc_patching.py | 219 +++++++++++++++++++++++++++++------ src/tycho/scattering.py | 63 ++++++++-- src/tycho/stellar_systems.py | 1 + 3 files changed, 242 insertions(+), 41 deletions(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 6f4de84..635e01c 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -39,6 +39,10 @@ def get_root_of_leaf(particle_set, chosen_id): def get_physical_radius(particle): + ''' + This is a very basic function which pulls an estimate for the radius of + a planet for use in Secular integrators. + ''' try: radius = particle.physical_radius return radius @@ -58,6 +62,10 @@ def get_physical_radius(particle): def get_full_hierarchical_structure(bodies, RelativePosition=False): + ''' + This function creates a tree-based particle set for use in hierarchical + particle integrators (like SecularMultiple). + ''' hierarchical_set = Particles() for body in bodies: print(body.id) @@ -80,7 +88,6 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): print("Closest Neighbor is:", closest_partner.id) # Check if closest_partner is in a binary already - #print(index) if index != 0: node = get_root_of_leaf(hierarchical_set, closest_partner.id) if node != None: @@ -114,8 +121,6 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): temp.velocity = bigbro.velocity print(bigbro.velocity) hierarchical_set.add_particle(temp) # Child1 is at -2 - #elif bigbro in hierarchical_set: - # hierarchical_set[] if lilsis not in hierarchical_set: temp = Particle() temp.id = lilsis.id @@ -152,14 +157,66 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): hierarchical_set.add_particle(root_particle) break continue - #for particle in hierarchical_set: - # if particle.is_binary == True: - # print(particle.child1.id, particle.child2.id) - # else: - # print(particle.id) return hierarchical_set.copy() +def check_for_stellar_collision(hierarchical_set): + ''' + This function checks for a planet entering into the Roche Limit + of its parent star. This is meant to be used as a check for Secular + orbit integrators. + ''' + map_node_oe_to_lilsis(hierarchical_set) + children_particles = hierarchical_set.select(lambda x : x == False, ["is_binary"]) + host_star = util.get_stars(children_particles)[0] + #print(host_star) + planets = util.get_planets(children_particles) + for planet in planets: + perihelion = planet.semimajor_axis*(1.0-planet.eccentricity) + roche_limit = 2.46*planet.radius*(host_star.mass/planet.mass)**(1/3.0) + #print("Perihelion:", perihelion, "| Roche Limit:", roche_limit) + if perihelion <= roche_limit: + # Host Star Gains Mass of Planet + host_star.mass += planet.mass + # Planet is removed from the set + planets.remove_particle(planet) + # Temporary Particle Set is Created with just Star and Planets + temp = Particles() + temp.add_particle(host_star) + temp.add_particles(planets) + # Update Position and Velocity Vectors + temp = update_posvel_from_oe(temp) + # Hierarchy is Rebuilt and Returned + return get_full_hierarchical_structure(temp) + return None + +def update_posvel_from_oe(particle_set): + ''' + This function generates position and velocity Vector + quantities for particles given their orbital elements. + This is meant to be used with SecularMultiple. + ''' + host_star = util.get_stars(particle_set) + planets = util.get_planets(particle_set) + host_star.position = [0.0, 0.0, 0.0] | units.AU + host_star.velocity = [0.0, 0.0, 0.0] | units.kms + temp = Particles() + temp.add_particle(host_star) + for planet in planets: + nbody_PlanetStarPair = \ + new_binary_from_orbital_elements(host_star.mass, planet.mass, planet.semimajor_axis, G=units.constants.G, \ + eccentricity = planet.eccentricity, inclination=planet.inclination, \ + longitude_of_the_ascending_node=planet.longitude_of_ascending_node, \ + argument_of_periapsis=planet.argument_of_pericenter, \ + true_anomaly = 360*rp.uniform(0.0,1.0) | units.deg) # random point in the orbit + planet.position = nbody_PlanetStarPair[1].position + planet.velocity = nbody_PlanetStarPair[1].velocity + temp.add_particle(planet) + temp[0].position += particle_set.center_of_mass() + temp[0].velocity += particle_set.center_of_mass_velocity() + return temp + def map_node_oe_to_lilsis(hierarchical_set): + ''' Maps Nodes' orbital elements to their child2 (lilsis) particle. ''' for node in hierarchical_set.select(lambda x : x == True, ["is_binary"]): lilsis = node.child2 lilsis.semimajor_axis = node.semimajor_axis @@ -170,6 +227,8 @@ def map_node_oe_to_lilsis(hierarchical_set): lilsis.period = node.period def reset_secularmultiples(code): + '''Useful function to reset the SecularMultiple integrator. + Saves on CPU overhead.''' unit_l = units.AU unit_m = units.MSun unit_t = 1.0e6*units.yr @@ -183,9 +242,42 @@ def reset_secularmultiples(code): code.error_code = 0 return code +def get_jovian_parent(hierarchical_set): + '''Gets the Node which contains the Jovian''' + nodes = hierarchical_set.select(lambda x : x == True, ["is_binary"]) + return [x for x in nodes if np.floor(x.child2.id/10000) == 5][0] + +def initialize_PlanetarySystem_from_HierarchicalSet(hierarchical_set): + '''Initializes a PlanetarySystem class for AMD calculations.''' + map_node_oe_to_lilsis(hierarchical_set) + children_particles = hierarchical_set.select(lambda x : x == False, ["is_binary"]) + host_star = util.get_stars(children_particles)[0] + #print(host_star) + planets = util.get_planets(children_particles) + PS = stellar_systems.PlanetarySystem(host_star, planets, system_name=str(host_star.id)) + PS.get_SystemBetaValues() + return PS + +def update_oe_for_PlanetarySystem(PS, hierarchical_set): + '''Updates the orbital elements from a hierarchical set to + an existant PlanetarySystem class.''' + map_node_oe_to_lilsis(hierarchical_set) + for planet in PS.planets: + matched_planet = hierarchical_set.select(lambda x : x == planet.id, ["id"]) + #print(planet.id) + #print(matched_planet.mass) + planet.semimajor_axis = matched_planet.semimajor_axis + planet.eccentricity = matched_planet.eccentricity + planet.period = matched_planet.period + planet.z_inc = matched_planet.inclination + PS.planets = PS.planets.sorted_by_attribute('semimajor_axis') + stellar_systems.get_rel_inclination(PS.planets) + return PS + def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ - N_output=100, debug_mode=False, genT4System=False, \ - exportData=False, GCode = None): + N_output=100, debug_mode=False, genT4System=True, \ + exportData=True, useAMD=True, fullCopy=False, GCode = None): + '''Does what it says on the tin.''' try: hierarchical_test = [x for x in particle_set if x.is_binary == True] print("The supplied set has", len(hierarchical_test), "node particles and is a tree.") @@ -204,20 +296,20 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ code = GCode if exportData: - plot_a_AU = [[] for x in range(Num_nodes)] - plot_e = [[] for x in range(Num_nodes)] - plot_peri_AU = [[] for x in range(Num_nodes)] - plot_stellar_inc_deg = [[] for x in range(Num_nodes)] + plot_a_AU = defaultdict(list) + plot_e = defaultdict(list) + plot_peri_AU = defaultdict(list) + plot_stellar_inc_deg = defaultdict(list) + if useAMD: + plot_AMDBeta = defaultdict(list) plot_times_Myr = [] if genT4System: - nodes = py_particles.select(lambda x : x == True, ["is_binary"]) print(nodes.inclination) nodes.inclination = [-18.137, 0.0, 23.570] | units.deg print(nodes.inclination) if debug_mode: - nodes = py_particles.select(lambda x : x == True, ["is_binary"]) print('='*50) print('t/kyr',0.00) print('a/AU', nodes.semimajor_axis) @@ -228,39 +320,65 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ nodes.argument_of_pericenter) print('LAN/deg', \ nodes.longitude_of_ascending_node) - + #print(py_particles) code.particles.add_particles(py_particles) - code.commit_particles() - code.model_time = start_time + #code.commit_particles() + #print(code.particles.semimajor_axis) + code.model_time = start_time + #print(py_particles.id, py_particles.semimajor_axis) channel_from_particles_to_code = py_particles.new_channel_to(code.particles) channel_from_code_to_particles = code.particles.new_channel_to(py_particles) - channel_from_particles_to_code.copy() - + #print(py_particles.id, py_particles.semimajor_axis) + if fullCopy: + channel_from_code_to_particles.copy() + else: + channel_from_particles_to_code.copy() #copy_attributes(['semimajor_axis', 'eccentricity', \ + #'longitude_of_ascending_node', 'argument_of_pericenter', 'inclination']) + #print('This is After the First Channel Copy:', code.particles.semimajor_axis) time = start_time - output_time_step = end_time/float(N_output) + if useAMD: + #print(py_particles.id, py_particles.mass) + jovianParent = get_jovian_parent(py_particles) + output_time_step = 1000*jovianParent.period.value_in(units.Myr) | units.Myr + PS = initialize_PlanetarySystem_from_HierarchicalSet(py_particles) + PS.get_SystemBetaValues() + if exportData: + for planet in PS.planets: + plot_AMDBeta[planet.id].append(planet.AMDBeta) + else: + output_time_step = end_time/float(N_output) if exportData: plot_times_Myr.append(time.value_in(units.Myr)) for i, node in enumerate(nodes): - plot_a_AU[i].append(node.semimajor_axis.value_in(units.AU)) - plot_e[i].append(node.eccentricity) - plot_peri_AU[i].append(node.semimajor_axis.value_in(units.AU)*(1.0-node.eccentricity)) - plot_stellar_inc_deg[i].append(node.inclination.value_in(units.deg)) + plot_a_AU[node.child2.id].append(node.semimajor_axis.value_in(units.AU)) + plot_e[node.child2.id].append(node.eccentricity) + plot_peri_AU[node.child2.id].append(node.semimajor_axis.value_in(units.AU)*(1.0-node.eccentricity)) + plot_stellar_inc_deg[node.child2.id].append(node.inclination.value_in(units.deg)) while time <= end_time: + #print('Start of Time Loop') + #print(output_time_step) time += output_time_step + #print(time) + #print(code.model_time) + #print(code.particles.semimajor_axis) code.evolve_model(time) - print('Evolved model to:', time.value_in(units.Myr), "Myr") + #print('Evolved model to:', time.value_in(units.Myr), "Myr") + #print(code.particles.semimajor_axis) channel_from_code_to_particles.copy() - + #channel_from_code_to_particles.copy_attributes(['semimajor_axis', 'eccentricity', \ + #'longitude_of_ascending_node', 'argument_of_pericenter', 'inclination']) + #print('Hello') + py_particles.time = time if exportData: plot_times_Myr.append(time.value_in(units.Myr)) for i, node in enumerate(nodes): - plot_a_AU[i].append(node.semimajor_axis.value_in(units.AU)) - plot_e[i].append(node.eccentricity) - plot_peri_AU[i].append(node.semimajor_axis.value_in(units.AU)*(1.0-node.eccentricity)) - plot_stellar_inc_deg[i].append(node.inclination.value_in(units.deg)) + plot_a_AU[node.child2.id].append(node.semimajor_axis.value_in(units.AU)) + plot_e[node.child2.id].append(node.eccentricity) + plot_peri_AU[node.child2.id].append(node.semimajor_axis.value_in(units.AU)*(1.0-node.eccentricity)) + plot_stellar_inc_deg[node.child2.id].append(node.inclination.value_in(units.deg)) if time == end_time+output_time_step: map_node_oe_to_lilsis(py_particles) @@ -277,12 +395,47 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ nodes.argument_of_pericenter) print('LAN/deg', \ nodes.longitude_of_ascending_node) + # Check for Planet Destruction from Star + #print(py_particles.id, py_particles.semimajor_axis) + temp = check_for_stellar_collision(py_particles) + #print(temp) + # Returns 'None' if No Destruction! + if temp != None: + code.stop() + code = SecularMultiple() + code.model_time = time + py_particles = Particles() + py_particles.add_particles(temp) + code.particles.add_particles(py_particles) + #code.commit_particles() + channel_from_particles_to_code = py_particles.new_channel_to(code.particles) + channel_from_code_to_particles = code.particles.new_channel_to(py_particles) + channel_from_particles_to_code.copy() + nodes = py_particles.select(lambda x : x == True, ["is_binary"]) + if useAMD: + PS = initialize_PlanetarySystem_from_HierarchicalSet(py_particles) + #channel_from_code_to_particles.copy_attributes(['semimajor_axis', 'eccentricity', \ + #'longitude_of_ascending_node', 'argument_of_pericenter', 'inclination']) + #print(code.particles.semimajor_axis) + # AMD Checking + if useAMD: + PS = update_oe_for_PlanetarySystem(PS, py_particles) + PS.get_SystemBetaValues() + if exportData: + for planet in PS.planets: + plot_AMDBeta[planet.id].append(planet.AMDBeta) + if len(PS.planets.select(lambda x : x < 1.0, ["AMDBeta"])) > 1: + break + if GCode == None: code.stop() else: code = reset_secularmultiples(code) if exportData: - data = plot_times_Myr,plot_a_AU, plot_e, plot_peri_AU, plot_stellar_inc_deg + if useAMD: + data = plot_times_Myr,plot_a_AU, plot_e, plot_peri_AU, plot_stellar_inc_deg, plot_AMDBeta + else: + data = plot_times_Myr,plot_a_AU, plot_e, plot_peri_AU, plot_stellar_inc_deg return py_particles, data else: return py_particles diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 73333fc..721a34b 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -15,7 +15,13 @@ from collections import defaultdict from amuse.community.secularmultiple.interface import SecularMultiple + from amuse.datamodel.trees import BinaryTreesOnAParticleSet +from amuse.ext.orbital_elements import new_binary_from_orbital_elements + +from amuse.community.smalln.interface import SmallN +from amuse.community.kepler.interface import Kepler +from amuse.community.ph4.interface import ph4 set_printing_strategy("custom", preferred_units = [units.MSun, units.AU, units.day, units.deg], precision = 6, prefix = "", separator = "[", suffix = "]") @@ -40,9 +46,34 @@ def build_ClusterEncounterHistory(rootExecDir): EncounterHistory[star_ID][RotationKey].append(str(rootExecDir+'/Scatter_IC/'+str(star_ID)+'/'+EncounterKey+'_'+RotationKey+'.hdf5')) return EncounterHistory +from amuse.community.secularmultiple.interface import SecularMultiple + +def initialize_GravCode(desiredCode, **kwargs): + converter = kwargs.get("converter", None) + n_workers = kwargs.get("number_of_workers", 1) + if converter == None: + converter = nbody_system.nbody_to_si(1 | units.MSun, 100 |units.AU) + GCode = desiredCode(number_of_workers = n_workers, redirection = "none", convert_nbody = converter) + GCode.initialize_code() + GCode.parameters.set_defaults() + if desiredCode == ph4: + GCode.parameters.timestep_parameter = 2.0**(-4.0) + if desiredCode == SmallN: + GCode.parameters.timestep_parameter = 0.05 + return GCode + +def initialize_isOverCode(**kwargs): + converter = kwargs.get("converter", None) + if converter == None: + converter = nbody_system.nbody_to_si(1 | units.MSun, 100 |units.AU) + isOverCode = SmallN(redirection = "none", convert_nbody = converter) + isOverCode.initialize_code() + isOverCode.parameters.set_defaults() + isOverCode.parameters.allow_full_unperturbed = 0 + return isOverCode class CloseEncounters(): - def __init__(self, Star_EncounterHistory, KeplerWorkerList = None): + def __init__(self, Star_EncounterHistory, KeplerWorkerList = None, NBodyWorkerList = None, SecularWorker = None): '''EncounterHistory should be a List of the Format {RotationKey: [Encounter0_FilePath, ...]}''' # Find the Main System's Host Star's ID and Assign it to 'KeySystemID' self.doEncounterPatching = True @@ -52,6 +83,8 @@ def __init__(self, Star_EncounterHistory, KeplerWorkerList = None): self.desired_endtime = 2.0 | units.Gyr self.max_end_time = 0.1 | units.Myr self.kep = KeplerWorkerList + self.NBodyCodes = NBodyWorkerList + self.SecularCode = SecularWorker # Create a List of StartingTimes and Encounter Initial Conditions (ICs) for all Orientations for RotationKey in Star_EncounterHistory.keys(): for i, Encounter in enumerate(Star_EncounterHistory[RotationKey]): @@ -71,6 +104,13 @@ def SimAllEncounters(self): self.kep.append(Kepler(unit_converter = converter, redirection = 'none')) self.kep[0].initialize_code() self.kep[1].initialize_code() + # Start up NBodyCodes if Needed + if self.NBodyCodes == None: + self.NBodyCodes = [initialize_GravCode(ph4), initialize_isOverCode()] + # Start up SecularCode if Needed + if self.SecularCode == None: + self.SecularCode = SecularMultiple() + # Begin Looping over Rotation Keys ... for RotationKey in self.ICs.keys(): for i in range(len(self.ICs[RotationKey])): @@ -89,7 +129,8 @@ def SimAllEncounters(self): else: current_max_endtime = self.StartTimes[RotationKey][i+1] EndingState = Encounter_Inst.SimSingleEncounter(current_max_endtime, \ - start_time = self.StartTimes[RotationKey][i]) + start_time = self.StartTimes[RotationKey][i], \ + GCodes = self.NBodyCodes) EndingStateTime = np.max(np.unique(EndingState.time)) print(Encounter_Inst.particles[0].position) @@ -116,8 +157,9 @@ def SimAllEncounters(self): # Simulate System till the Next Encounter's Start Time Encounter_Inst = self.SingleEncounter(EndingState) - FinalState = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], - start_time = EndingStateTime) + FinalState = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], \ + start_time = EndingStateTime, \ + GCode = self.SecularCode) # Begin Patching of the End State to the Next Encounter self.ICs[RotationKey][i+1] = self.PatchedEncounter(FinalState, NextEncounter) @@ -126,7 +168,9 @@ def SimAllEncounters(self): #print(CurrentEncounter[0].time.value_in(units.Myr)) #print(EndingState[0].time.value_in(units.Myr)) Encounter_Inst = self.SingleEncounter(EndingState) - FinalState = Encounter_Inst.SimSecularSystem(self.desired_endtime, start_time = EndingStateTime) + FinalState = Encounter_Inst.SimSecularSystem(self.desired_endtime, \ + start_time = EndingStateTime, \ + GCode=self.SecularCode) #print(FinalState[0].position) # Append the FinalState of Each Encounter to its Dictionary @@ -196,8 +240,10 @@ def __init__(self, EncounterBodies): def SimSecularSystem(self, desired_end_time, **kwargs): start_time = kwargs.get("start_time", 0 | units.Myr) + GCode = kwargs.get("GCode", None) self.particles = enc_patching.run_secularmultiple(self.particles, desired_end_time, \ - start_time = start_time, N_output=5) + start_time = start_time, N_output=5, \ + GCode=GCode) return self.particles def SimSingleEncounter(self, max_end_time, **kwargs): @@ -215,8 +261,8 @@ def SimSingleEncounter(self, max_end_time, **kwargs): if converter == None: converter = nbody_system.nbody_to_si(GravitatingBodies.mass.sum(), \ 2 * np.max(GravitatingBodies.radius.number) | GravitatingBodies.radius.unit) - gravity = self.initialize_GravCode(ph4, converter=converter) - over_grav = self.initialize_isOverCode(converter=converter) + gravity = initialize_GravCode(ph4, converter=converter) + over_grav = initialize_isOverCode(converter=converter) else: gravity = GCodes[0] over_grav = GCodes[1] @@ -295,6 +341,7 @@ def SimSingleEncounter(self, max_end_time, **kwargs): gravity.stop() over_grav.stop() else: + # Reset the Gravity Codes Once Encounter Finishes gravity.reset() over_grav.reset() diff --git a/src/tycho/stellar_systems.py b/src/tycho/stellar_systems.py index 8f624ab..629f74d 100644 --- a/src/tycho/stellar_systems.py +++ b/src/tycho/stellar_systems.py @@ -50,6 +50,7 @@ def get_periods(host_star, planets): for planet in planets: mu = constants.G*(planet.mass+host_star.mass) a = planet.semimajor_axis + print(mu, a, 2.0*np.pi/np.sqrt(mu)*a**(3./2.)) planet.period = 2.0*np.pi/np.sqrt(mu)*a**(3./2.) def update_orb_elem(host_star, planets, converter=None, kepler_worker=None): From e0f4fca273274665d44cff5694f47018d3381e9c Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 10 Aug 2020 15:41:30 -0400 Subject: [PATCH 029/130] Small change to remove old copy argument and change some defaults. --- src/tycho/enc_patching.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 635e01c..77b396f 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -275,8 +275,8 @@ def update_oe_for_PlanetarySystem(PS, hierarchical_set): return PS def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ - N_output=100, debug_mode=False, genT4System=True, \ - exportData=True, useAMD=True, fullCopy=False, GCode = None): + N_output=100, debug_mode=False, genT4System=False, \ + exportData=True, useAMD=True, GCode = None): '''Does what it says on the tin.''' try: hierarchical_test = [x for x in particle_set if x.is_binary == True] @@ -330,10 +330,8 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ channel_from_particles_to_code = py_particles.new_channel_to(code.particles) channel_from_code_to_particles = code.particles.new_channel_to(py_particles) #print(py_particles.id, py_particles.semimajor_axis) - if fullCopy: - channel_from_code_to_particles.copy() - else: - channel_from_particles_to_code.copy() #copy_attributes(['semimajor_axis', 'eccentricity', \ + + channel_from_particles_to_code.copy() #copy_attributes(['semimajor_axis', 'eccentricity', \ #'longitude_of_ascending_node', 'argument_of_pericenter', 'inclination']) #print('This is After the First Channel Copy:', code.particles.semimajor_axis) time = start_time From 5252767d272cfc1f32122837af3e904e9558f958 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 10 Aug 2020 15:56:06 -0400 Subject: [PATCH 030/130] Added needed import. --- src/tycho/enc_patching.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 77b396f..e12fe83 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -7,6 +7,8 @@ import hashlib +from collections import defaultdict + from amuse.community.secularmultiple.interface import SecularMultiple from amuse.datamodel.trees import BinaryTreesOnAParticleSet from amuse.ext.orbital_elements import get_orbital_elements_from_binary From 549cdead4d6277c828d7e997c77e1d3f242b1d2f Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 10 Aug 2020 17:01:06 -0400 Subject: [PATCH 031/130] Quick addition to have AMD ending check happen every 100 timesteps. --- src/tycho/enc_patching.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index e12fe83..0befc4b 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -356,11 +356,12 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ plot_e[node.child2.id].append(node.eccentricity) plot_peri_AU[node.child2.id].append(node.semimajor_axis.value_in(units.AU)*(1.0-node.eccentricity)) plot_stellar_inc_deg[node.child2.id].append(node.inclination.value_in(units.deg)) - + counter = 0 while time <= end_time: #print('Start of Time Loop') #print(output_time_step) time += output_time_step + counter += 1 #print(time) #print(code.model_time) #print(code.particles.semimajor_axis) @@ -424,7 +425,7 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ if exportData: for planet in PS.planets: plot_AMDBeta[planet.id].append(planet.AMDBeta) - if len(PS.planets.select(lambda x : x < 1.0, ["AMDBeta"])) > 1: + if counter%100==0 and len(PS.planets.select(lambda x : x < 1.0, ["AMDBeta"])) > 1: break if GCode == None: From f4c775ac56f5ccf5a52f7bceda31f213e9866afc Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 15:08:28 -0400 Subject: [PATCH 032/130] Small update to log. --- cut_encounters.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cut_encounters.py b/cut_encounters.py index 363a787..2711483 100644 --- a/cut_encounters.py +++ b/cut_encounters.py @@ -146,6 +146,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): sys.stdout.flush() print(util.timestamp(), "Performing First Cut on Encounter Database ...") + print(encounter_db.keys()) sys.stdout.flush() # Perform a Cut on the Encounter Database for star_ID in list(encounter_db.keys()): @@ -166,7 +167,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): enc_id_to_cut.append(enc_id) for enc_id in sorted(enc_id_to_cut, reverse=True): del encounter_db[star_ID][enc_id] - + print(encounter_db.keys()) sys.stdout.flush() print(util.timestamp(), "Performing Second Cut on Encounter Database ...") sys.stdout.flush() @@ -194,9 +195,10 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): for star_ID in list(encounter_db.keys()): if len(encounter_db[star_ID]) == 0: star_id_to_cut.append(star_ID) + print("Star IDs to Cut:", star_id_to_cut) for star_ID in sorted(star_id_to_cut, reverse=True): del encounter_db[star_ID] - + print(encounter_db.keys()) encounter_cut_file = open(os.getcwd()+"/"+cluster_name+"_encounters_cut.pkl", "wb") pickle.dump(encounter_db, encounter_cut_file) encounter_cut_file.close() From f5a851523e2cd8ee10ebf4655e0c1c1d074f4bc8 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 16:40:00 -0400 Subject: [PATCH 033/130] Small change to encounter cutting. --- cut_encounters.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cut_encounters.py b/cut_encounters.py index 2711483..f322db4 100644 --- a/cut_encounters.py +++ b/cut_encounters.py @@ -152,8 +152,10 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): for star_ID in list(encounter_db.keys()): # Cut Out Stars Recorded with Only Initialization Pickups if len(encounter_db[star_ID]) <= 1: - del encounter_db[star_ID] - continue + # Check to Ensure it is an Actual Multiples Initialization (AKA: 1 System) + if len(stellar_systems.get_heirarchical_systems_from_set(encounter_db[star_ID][0]) <= 1: + del encounter_db[star_ID] + print("After Removal of Just Initializations", encounter_db.keys()) for star_ID in list(encounter_db.keys()): # Cut Out Stars with No Planets enc_id_to_cut = [] From d97849fac9606b29bb269cfdcdcd433f0c937068 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 16:45:05 -0400 Subject: [PATCH 034/130] Bit more bug fixing of addition. --- cut_encounters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cut_encounters.py b/cut_encounters.py index f322db4..7585f81 100644 --- a/cut_encounters.py +++ b/cut_encounters.py @@ -153,7 +153,8 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): # Cut Out Stars Recorded with Only Initialization Pickups if len(encounter_db[star_ID]) <= 1: # Check to Ensure it is an Actual Multiples Initialization (AKA: 1 System) - if len(stellar_systems.get_heirarchical_systems_from_set(encounter_db[star_ID][0]) <= 1: + if len(stellar_systems.get_heirarchical_systems_from_set(encounter_db[star_ID][0])) <= 1: + print(encounter_db[starID][0].id) del encounter_db[star_ID] print("After Removal of Just Initializations", encounter_db.keys()) for star_ID in list(encounter_db.keys()): From 4338c40d2ef5c485aad65663b8fbecaeaa714179 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 16:46:50 -0400 Subject: [PATCH 035/130] Small fix to arguments in if. --- cut_encounters.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cut_encounters.py b/cut_encounters.py index 7585f81..4720997 100644 --- a/cut_encounters.py +++ b/cut_encounters.py @@ -151,7 +151,9 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): # Perform a Cut on the Encounter Database for star_ID in list(encounter_db.keys()): # Cut Out Stars Recorded with Only Initialization Pickups - if len(encounter_db[star_ID]) <= 1: + if len(encounter_dB[star_ID]) == 0: + del encounter_db[star_ID] + if len(encounter_db[star_ID]) == 1: # Check to Ensure it is an Actual Multiples Initialization (AKA: 1 System) if len(stellar_systems.get_heirarchical_systems_from_set(encounter_db[star_ID][0])) <= 1: print(encounter_db[starID][0].id) From 87e9eb4ad0badbcf6a4d9989d2d46da0ea3f12f8 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 16:47:39 -0400 Subject: [PATCH 036/130] Syntax error. --- cut_encounters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cut_encounters.py b/cut_encounters.py index 4720997..a13ebb0 100644 --- a/cut_encounters.py +++ b/cut_encounters.py @@ -151,7 +151,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): # Perform a Cut on the Encounter Database for star_ID in list(encounter_db.keys()): # Cut Out Stars Recorded with Only Initialization Pickups - if len(encounter_dB[star_ID]) == 0: + if len(encounter_db[star_ID]) == 0: del encounter_db[star_ID] if len(encounter_db[star_ID]) == 1: # Check to Ensure it is an Actual Multiples Initialization (AKA: 1 System) From abc0c6001e80e09f4f1e3c645a3f09942e31bfc0 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 16:50:27 -0400 Subject: [PATCH 037/130] Small change to logging. --- cut_encounters.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cut_encounters.py b/cut_encounters.py index a13ebb0..c6a5d07 100644 --- a/cut_encounters.py +++ b/cut_encounters.py @@ -146,7 +146,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): sys.stdout.flush() print(util.timestamp(), "Performing First Cut on Encounter Database ...") - print(encounter_db.keys()) + print(len(encounter_db.keys())) sys.stdout.flush() # Perform a Cut on the Encounter Database for star_ID in list(encounter_db.keys()): @@ -155,10 +155,12 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): del encounter_db[star_ID] if len(encounter_db[star_ID]) == 1: # Check to Ensure it is an Actual Multiples Initialization (AKA: 1 System) - if len(stellar_systems.get_heirarchical_systems_from_set(encounter_db[star_ID][0])) <= 1: + temp = stellar_systems.get_heirarchical_systems_from_set(encounter_db[star_ID][0]) + print(temp) + if len(temp.keys()) <= 1: print(encounter_db[starID][0].id) del encounter_db[star_ID] - print("After Removal of Just Initializations", encounter_db.keys()) + print("After Removal of Just Initializations", len(encounter_db.keys())) for star_ID in list(encounter_db.keys()): # Cut Out Stars with No Planets enc_id_to_cut = [] From 691fcd85a0201e297405d2ef99ff2005d3e0eef2 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 16:53:06 -0400 Subject: [PATCH 038/130] Further changes. --- cut_encounters.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cut_encounters.py b/cut_encounters.py index c6a5d07..cb7b675 100644 --- a/cut_encounters.py +++ b/cut_encounters.py @@ -167,6 +167,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): for enc_id, encounter in enumerate(encounter_db[star_ID]): # Refine "No Planet" Cut to Deal with Hierarchical Stellar Systems # We are Looping Through Encounters to Deal with Rogue Jupiter Captures + print(star_ID, encounter.id) if len([ID for ID in encounter.id if ID >= base_planet_ID]) == 0: enc_id_to_cut.append(enc_id) elif len([ID for ID in encounter.id if ID >= base_planet_ID]) > 0: From fa9d4b205cf54431782fffc779e9bd3c108ffd91 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 16:54:38 -0400 Subject: [PATCH 039/130] Print statement. --- cut_encounters.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cut_encounters.py b/cut_encounters.py index cb7b675..1765a97 100644 --- a/cut_encounters.py +++ b/cut_encounters.py @@ -168,6 +168,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): # Refine "No Planet" Cut to Deal with Hierarchical Stellar Systems # We are Looping Through Encounters to Deal with Rogue Jupiter Captures print(star_ID, encounter.id) + sys.stdout.flush() if len([ID for ID in encounter.id if ID >= base_planet_ID]) == 0: enc_id_to_cut.append(enc_id) elif len([ID for ID in encounter.id if ID >= base_planet_ID]) > 0: From 56f86cecdb3ce45795d12aa909115c9984411b87 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 16:59:49 -0400 Subject: [PATCH 040/130] Few more debug prints. --- cut_encounters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cut_encounters.py b/cut_encounters.py index 1765a97..6f72fee 100644 --- a/cut_encounters.py +++ b/cut_encounters.py @@ -176,7 +176,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): enc_id_to_cut.append(enc_id) for enc_id in sorted(enc_id_to_cut, reverse=True): del encounter_db[star_ID][enc_id] - print(encounter_db.keys()) + print("After no planet encounters are removed," len(encounter_db.keys())) sys.stdout.flush() print(util.timestamp(), "Performing Second Cut on Encounter Database ...") sys.stdout.flush() @@ -185,6 +185,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): for star_ID in list(encounter_db.keys()): if len(encounter_db[star_ID]) == 0: star_id_to_cut.append(star_ID) + print(len(star_id_to_cut)) for star_ID in sorted(star_id_to_cut, reverse=True): del encounter_db[star_ID] From 8e04a55d3170c91d685870caca38adec3a1083f8 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 17:00:47 -0400 Subject: [PATCH 041/130] Syntax error --- cut_encounters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cut_encounters.py b/cut_encounters.py index 6f72fee..ca2b865 100644 --- a/cut_encounters.py +++ b/cut_encounters.py @@ -176,7 +176,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): enc_id_to_cut.append(enc_id) for enc_id in sorted(enc_id_to_cut, reverse=True): del encounter_db[star_ID][enc_id] - print("After no planet encounters are removed," len(encounter_db.keys())) + print("After no planet encounters are removed", len(encounter_db.keys())) sys.stdout.flush() print(util.timestamp(), "Performing Second Cut on Encounter Database ...") sys.stdout.flush() From e2b438e0fa3f5200fa35ce38cde900b439fbe621 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 17:13:20 -0400 Subject: [PATCH 042/130] Change to how classes work in Py3 --- sim_cluster.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sim_cluster.py b/sim_cluster.py index 9f055e3..362777a 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -84,6 +84,9 @@ def print_diagnostics(grav, E0=None): class EncounterHandler(object): + def __init__(self): + self.encounterInformation = defaultdict(list) + def handle_encounter_v5(self, time, star1, star2): # Create the Scattering CoM Particle Set scattering_com = Particles(particles = (star1, star2)) @@ -108,7 +111,7 @@ def handle_encounter_v5(self, time, star1, star2): # Retrieve Star IDs to Use as Dictionary Keys, and Loop Over Those IDs # to Add Encounter Information to Each Star's Dictionary Entry. for s_id in [str(dict_key) for dict_key in enc_particles.id if dict_key<=len(Gravitating_Bodies)]: - encounterInformation[s_id].append(enc_particles) + self.encounterInformation[s_id].append(enc_particles) # Return True is Necessary for the Multiples Code return True @@ -405,8 +408,7 @@ def move_particle(set_from, set_to, particle_id): gravity_constant=units.constants.G) multiples_code.neighbor_perturbation_limit = 0.05 multiples_code.neighbor_veto = True - multiples_code.callback = EncounterHandler().handle_encounter_v5 - multiples_code.global_debug = 1 + multiples_code.global_debug = 2 # ---------------------------------------------------------------------------------------------------- # Setting up Stellar Evolution Code (SeBa) @@ -463,6 +465,9 @@ def move_particle(set_from, set_to, particle_id): for star in Individual_Stars: dict_key = str(star.id) encounterInformation[dict_key] = [] + EH = EncounterHandler() + EH.encounterInformation = encounterInformation + multiples_code.callback = EH.handle_encounter_v5 snapshots_dir = os.getcwd()+"/Snapshots" snapshots_s_dir = os.getcwd()+"/Snapshots/Stars" From 7b27fd2155502dadfc63c7224ed39e484808f094 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 17:46:31 -0400 Subject: [PATCH 043/130] Adding debug. --- sim_cluster.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sim_cluster.py b/sim_cluster.py index 362777a..e75b97d 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -107,10 +107,13 @@ def handle_encounter_v5(self, time, star1, star2): limiting_mass_for_planets = 13 | units.MJupiter enc_stars = enc_particles[enc_particles.mass > limiting_mass_for_planets] enc_planets = enc_particles[enc_particles.mass <= limiting_mass_for_planets] + print("Stars:", enc_stars.id) + print("Planets:", enc_planets.id) # Retrieve Star IDs to Use as Dictionary Keys, and Loop Over Those IDs # to Add Encounter Information to Each Star's Dictionary Entry. for s_id in [str(dict_key) for dict_key in enc_particles.id if dict_key<=len(Gravitating_Bodies)]: + print(s_id) self.encounterInformation[s_id].append(enc_particles) # Return True is Necessary for the Multiples Code From ead7a6c439c070c03104aeeea5ba3503a6a1ae34 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 20:51:25 -0400 Subject: [PATCH 044/130] Fixes to the encounter debug. --- sim_cluster.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/sim_cluster.py b/sim_cluster.py index e75b97d..0e1c74f 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -85,7 +85,7 @@ def print_diagnostics(grav, E0=None): class EncounterHandler(object): def __init__(self): - self.encounterInformation = defaultdict(list) + self.encounterDict = defaultdict(list) def handle_encounter_v5(self, time, star1, star2): # Create the Scattering CoM Particle Set @@ -110,11 +110,16 @@ def handle_encounter_v5(self, time, star1, star2): print("Stars:", enc_stars.id) print("Planets:", enc_planets.id) + IDs_of_StarsInEncounter = [star.id for star in enc_stars if star.id < 1000000] + print(IDs_of_StarsInEncounter) + for star_ID in IDs_of_StarsInEncounter: + self.encounterDict[star_ID].append(enc_particles) + # Retrieve Star IDs to Use as Dictionary Keys, and Loop Over Those IDs # to Add Encounter Information to Each Star's Dictionary Entry. - for s_id in [str(dict_key) for dict_key in enc_particles.id if dict_key<=len(Gravitating_Bodies)]: - print(s_id) - self.encounterInformation[s_id].append(enc_particles) + #for s_id in [str(dict_key) for dict_key in enc_particles.id if dict_key<=len(Gravitating_Bodies)]: + # print(s_id) + # self.encounterDict[s_id].append(enc_particles) # Return True is Necessary for the Multiples Code return True @@ -464,12 +469,11 @@ def move_particle(set_from, set_to, particle_id): # Each Key (Star's ID) will Associate with a List of Encounter Particle # Sets as Encounters are Detected encounter_file = None - encounterInformation = defaultdict(list) - for star in Individual_Stars: - dict_key = str(star.id) - encounterInformation[dict_key] = [] + #encounterInformation = defaultdict(list) + #for star in Individual_Stars: + # dict_key = str(star.id) + # encounterInformation[dict_key] = [] EH = EncounterHandler() - EH.encounterInformation = encounterInformation multiples_code.callback = EH.handle_encounter_v5 snapshots_dir = os.getcwd()+"/Snapshots" From 6a0d1e4768c960ba35ebe565b1d65cfd4513c50d Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 20:56:54 -0400 Subject: [PATCH 045/130] Small mistake in rename of variable. --- sim_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim_cluster.py b/sim_cluster.py index 0e1c74f..6451280 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -601,7 +601,7 @@ def move_particle(set_from, set_to, particle_id): cluster_name+"_encounters_backup.pkl") # Finally, Save the Encounter Dictionary! encounter_file = open(cluster_name+"_encounters.pkl", "wb") - pickle.dump(encounterInformation, encounter_file) + pickle.dump(EH.encounterDict, encounter_file) encounter_file.close() # Log that a the Encounters have been Saved! print('\n-------------') From d0671b36b60d09d8554ea1faf6931aa81bb7a030 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 21:39:56 -0400 Subject: [PATCH 046/130] adding more debug --- sim_cluster.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sim_cluster.py b/sim_cluster.py index 6451280..23cbe7c 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -89,13 +89,23 @@ def __init__(self): def handle_encounter_v5(self, time, star1, star2): # Create the Scattering CoM Particle Set - scattering_com = Particles(particles = (star1, star2)) + scattering_com = Particles() + scattering_com.add_particle(star1) + scattering_com.add_particle(star2) com_pos = scattering_com.center_of_mass() com_vel = scattering_com.center_of_mass_velocity() # Expand enconter returns a particle set with all of the children # when given a particle set of two objects involved in an encounter enc_particles = multiples_code.expand_encounter(scattering_com, delete=False)[0] + try: + print("Scattering:", scattering_com.id, scattering_com.child1.id, scattering_com.child2.id) + except: + print("Scattering:", scattering_com.id, scattering_com.child1, scattering_com.child2) + try: + print("EncPart:", enc_particles.id, enc_particles.child1.id, enc_particles.child2.id) + except: + print("EncPart:", enc_particles.id, enc_particles.child1, enc_particles.child2) # Assign the time of the encounter to the Encounter Particle Set. enc_particles.time = time From 38dc440474b06b5244b62b094a9c70787e9a1d62 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 22:03:42 -0400 Subject: [PATCH 047/130] Attempt a fix. --- sim_cluster.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sim_cluster.py b/sim_cluster.py index 23cbe7c..63bdd14 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -89,9 +89,9 @@ def __init__(self): def handle_encounter_v5(self, time, star1, star2): # Create the Scattering CoM Particle Set - scattering_com = Particles() - scattering_com.add_particle(star1) - scattering_com.add_particle(star2) + scattering_com = self.before + #scattering_com.add_particle(star1) + #scattering_com.add_particle(star2) com_pos = scattering_com.center_of_mass() com_vel = scattering_com.center_of_mass_velocity() From 605592e575a2425143a987f0f4e5ddaa26a6a1d3 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 22:04:29 -0400 Subject: [PATCH 048/130] Needs to be from multiples_code not EH. --- sim_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim_cluster.py b/sim_cluster.py index 63bdd14..85ec35d 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -89,7 +89,7 @@ def __init__(self): def handle_encounter_v5(self, time, star1, star2): # Create the Scattering CoM Particle Set - scattering_com = self.before + scattering_com = multiples_code.before #scattering_com.add_particle(star1) #scattering_com.add_particle(star2) com_pos = scattering_com.center_of_mass() From 64b1a50b55a4fbe266cec8a8cc5608c5db3b1825 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 22:18:43 -0400 Subject: [PATCH 049/130] Attempting bug hunt. --- sim_cluster.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sim_cluster.py b/sim_cluster.py index 85ec35d..94639d8 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -89,6 +89,7 @@ def __init__(self): def handle_encounter_v5(self, time, star1, star2): # Create the Scattering CoM Particle Set + print("Star 1", star1.child1.id, star2.child2.id) scattering_com = multiples_code.before #scattering_com.add_particle(star1) #scattering_com.add_particle(star2) From 2f08618ebd19eef150a3c5b38f6d56bc25a31dbd Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 11 Aug 2020 22:37:12 -0400 Subject: [PATCH 050/130] Debugging. --- sim_cluster.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sim_cluster.py b/sim_cluster.py index 94639d8..71895e3 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -89,10 +89,14 @@ def __init__(self): def handle_encounter_v5(self, time, star1, star2): # Create the Scattering CoM Particle Set - print("Star 1", star1.child1.id, star2.child2.id) - scattering_com = multiples_code.before - #scattering_com.add_particle(star1) - #scattering_com.add_particle(star2) + #print("Star 1", star1.child1.id, star2.child2.id) + print("!-----------------------------") + print(star1) + print(star2) + print("!-----------------------------") + scattering_com = Particles() + scattering_com.add_particle(star1) + scattering_com.add_particle(star2) com_pos = scattering_com.center_of_mass() com_vel = scattering_com.center_of_mass_velocity() From 8139bf183405fac08725e6bff6d38f5b6465b69a Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 12 Aug 2020 15:52:21 -0400 Subject: [PATCH 051/130] Made changes to Encounter Logger and temporarily switched multiples mod --- sim_cluster.py | 79 +- src/tycho/{ => archive}/analysis_old.py | 0 src/tycho/{ => archive}/brokenimf.py | 0 src/tycho/{ => archive}/multiples2.py | 0 src/tycho/{ => archive}/multiples3.py | 0 src/tycho/{ => archive}/multiples4.py | 0 src/tycho/multiples.py | 3171 +++++++++++++++++++++++ src/tycho/multiples2.pyc | Bin 49595 -> 0 bytes src/tycho/multiples3.pyc | Bin 49761 -> 0 bytes 9 files changed, 3196 insertions(+), 54 deletions(-) rename src/tycho/{ => archive}/analysis_old.py (100%) rename src/tycho/{ => archive}/brokenimf.py (100%) rename src/tycho/{ => archive}/multiples2.py (100%) rename src/tycho/{ => archive}/multiples3.py (100%) rename src/tycho/{ => archive}/multiples4.py (100%) create mode 100644 src/tycho/multiples.py delete mode 100644 src/tycho/multiples2.pyc delete mode 100644 src/tycho/multiples3.pyc diff --git a/sim_cluster.py b/sim_cluster.py index 71895e3..366ec37 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -52,8 +52,8 @@ # Import the Tycho Packages from tycho import create, util, read, write, encounter_db -#from tycho import multiples3 as multiples -import amuse.couple.multiples as multiples +from tycho import multiples as multiples +#import amuse.couple.multiples as multiples # ------------------------------------- # @@ -86,57 +86,32 @@ def print_diagnostics(grav, E0=None): class EncounterHandler(object): def __init__(self): self.encounterDict = defaultdict(list) + self.debug_mode = 0 + self.limiting_mass_for_planets = 13 | units.MJupiter - def handle_encounter_v5(self, time, star1, star2): - # Create the Scattering CoM Particle Set - #print("Star 1", star1.child1.id, star2.child2.id) - print("!-----------------------------") - print(star1) - print(star2) - print("!-----------------------------") - scattering_com = Particles() - scattering_com.add_particle(star1) - scattering_com.add_particle(star2) - com_pos = scattering_com.center_of_mass() - com_vel = scattering_com.center_of_mass_velocity() - - # Expand enconter returns a particle set with all of the children - # when given a particle set of two objects involved in an encounter - enc_particles = multiples_code.expand_encounter(scattering_com, delete=False)[0] - try: - print("Scattering:", scattering_com.id, scattering_com.child1.id, scattering_com.child2.id) - except: - print("Scattering:", scattering_com.id, scattering_com.child1, scattering_com.child2) - try: - print("EncPart:", enc_particles.id, enc_particles.child1.id, enc_particles.child2.id) - except: - print("EncPart:", enc_particles.id, enc_particles.child1, enc_particles.child2) - - # Assign the time of the encounter to the Encounter Particle Set. - enc_particles.time = time + def log_encounter(self, time, particles_in_encounter): + # Initialize the Temporary Particle Set to Ensure Nothing + # Changes inside "particles_in_encounter" + _temp = Particles() + _temp.add_particles(particles_in_encounter) # Set the Origin to be the Center of Mass for the Encounter Particle Set. - enc_particles.position -= com_pos - enc_particles.velocity -= com_vel - - limiting_mass_for_planets = 13 | units.MJupiter - enc_stars = enc_particles[enc_particles.mass > limiting_mass_for_planets] - enc_planets = enc_particles[enc_particles.mass <= limiting_mass_for_planets] - print("Stars:", enc_stars.id) - print("Planets:", enc_planets.id) + _temp.position -= particles_in_encounter.center_of_mass() + _temp.velocity -= particles_in_encounter.center_of_mass_velocity() + _temp.time = time + # Seperate out Stars to Nab Keys for EncounterDictionary Logging + enc_stars = _temp[_temp > self.limiting_mass_for_planets] IDs_of_StarsInEncounter = [star.id for star in enc_stars if star.id < 1000000] - print(IDs_of_StarsInEncounter) for star_ID in IDs_of_StarsInEncounter: - self.encounterDict[star_ID].append(enc_particles) - - # Retrieve Star IDs to Use as Dictionary Keys, and Loop Over Those IDs - # to Add Encounter Information to Each Star's Dictionary Entry. - #for s_id in [str(dict_key) for dict_key in enc_particles.id if dict_key<=len(Gravitating_Bodies)]: - # print(s_id) - # self.encounterDict[s_id].append(enc_particles) - - # Return True is Necessary for the Multiples Code + self.encounterDict[star_ID].append(_temp) + if debug_mode > 0: + enc_planets = _temp[_temp.mass <= limiting_mass_for_planets] + print("Stars:", enc_stars.id) + print("Planets:", enc_planets.id) + print("Keys Set for EncounterDictionary:", IDs_of_StarsInEncounter) + # Delete the Temporary Particle Set + del _temp return True class ChildUpdater(object): @@ -484,12 +459,9 @@ def move_particle(set_from, set_to, particle_id): # Each Key (Star's ID) will Associate with a List of Encounter Particle # Sets as Encounters are Detected encounter_file = None - #encounterInformation = defaultdict(list) - #for star in Individual_Stars: - # dict_key = str(star.id) - # encounterInformation[dict_key] = [] EH = EncounterHandler() - multiples_code.callback = EH.handle_encounter_v5 + EH.debug_mode = 1 + multiples_code.encounterLogger = EH.log_encounter snapshots_dir = os.getcwd()+"/Snapshots" snapshots_s_dir = os.getcwd()+"/Snapshots/Stars" @@ -573,7 +545,7 @@ def move_particle(set_from, set_to, particle_id): #subset_sync.update_children_bodies(multiples_code, Individual_Stars, Planets) # Evolve the Stellar Codes (via SEV Code with Channels) - # TODO: Ensure Binaries are Evolved Correctly (See Section 3.2.8) + # TODO: Ensure Tight Binaries are Evolved Correctly (See Section 3.2.8) sev_code.evolve_model(t_current) # Sync the Stellar Code w/ the "Stellar_Bodies" Superset @@ -581,7 +553,6 @@ def move_particle(set_from, set_to, particle_id): "temperature", "age"]) # Sync the Multiples Particle Set's Masses to the Stellar_Bodies' Masses - # TODO: Ensure that the channel_from_gravitating_to_multi.copy_attributes(["mass"]) # Note: The "mass" Attribute in "Gravitating_Bodies" is synced when "Stellar_Bodies" is. diff --git a/src/tycho/analysis_old.py b/src/tycho/archive/analysis_old.py similarity index 100% rename from src/tycho/analysis_old.py rename to src/tycho/archive/analysis_old.py diff --git a/src/tycho/brokenimf.py b/src/tycho/archive/brokenimf.py similarity index 100% rename from src/tycho/brokenimf.py rename to src/tycho/archive/brokenimf.py diff --git a/src/tycho/multiples2.py b/src/tycho/archive/multiples2.py similarity index 100% rename from src/tycho/multiples2.py rename to src/tycho/archive/multiples2.py diff --git a/src/tycho/multiples3.py b/src/tycho/archive/multiples3.py similarity index 100% rename from src/tycho/multiples3.py rename to src/tycho/archive/multiples3.py diff --git a/src/tycho/multiples4.py b/src/tycho/archive/multiples4.py similarity index 100% rename from src/tycho/multiples4.py rename to src/tycho/archive/multiples4.py diff --git a/src/tycho/multiples.py b/src/tycho/multiples.py new file mode 100644 index 0000000..3e4000f --- /dev/null +++ b/src/tycho/multiples.py @@ -0,0 +1,3171 @@ +import sys +import numpy +import collections +import math +import copy + +from amuse.datamodel import particle_attributes +from amuse.datamodel import trees +from amuse.datamodel import Particle, Particles +from amuse.rfi.core import is_mpd_running +from amuse.ic.plummer import new_plummer_model +from amuse.ic.salpeter import new_salpeter_mass_distribution_nbody + +from amuse.units import nbody_system +from amuse.units import units +from amuse.units import constants +from amuse.units import quantities +from amuse.units.quantities import zero +from amuse.support.exceptions import KeysNotInStorageException +from amuse import io + +#--------------------------------------------------------------------- +# +# Steve's ToDo list of features to be added/improved in the multiples +# module. +# +# 1. Should use only perturbers (within ~100 x interaction scale) in +# computing the tidal energy change, not the entire system. +# +# 2. If the gravity module supports it, only perturbers need be +# synchronized before and reinitialized after the interaction. +# +# 3. Including near neighbors in a 2-body interaction is likely to +# lead to spurious binaries, since the 3- (or more-) body interaction +# will be followed to completion, when in fact it should be stopped +# when the neighbor has left the interaction region. The result is +# that binaries may form prematurely. If we want to include +# neighbors, we should also implement item 4 below, to allow long +# interactions to be broken into pieces. Including neighbors in the +# interaction may also lead to problematic final configurations and +# large internal/external tidal errors. Alternatively, we can let +# near neighbors simply veto the encounter, moving the work back into +# the gravity module, until a "clean" 2-body scattering can be +# identified. Vetoing is now the default. +# +# 4. We should seek a better prescription for compressing 3-body and +# higher-order configurations. Currently we conserve energy, but not +# angular momentum. +# +# 5. There is no provision for physical collisions in the smallN code, +# and no logic in the Multiples module to manage stars having both +# dynamical and physical radii. +# +#--------------------------------------------------------------------- + +# The following simple CM indexing scheme is OK for N < 1000000. An +# improved scheme might be desirable, but it must be compatible with +# the integer IDs used in most gravity modules. + +root_index = 1000000 + +def new_root_index(): + global root_index + root_index += 1 + return root_index + +def name_object(tree): + name = "{ " + if hasattr(tree, "child1"): + children = [tree.child1, tree.child2] + else: + #children = tree.get_tree_subset() + children = [tree.particle.child1, tree.particle.child2] + for child in sorted(children, \ + key=lambda x: x.id if hasattr(x ,"id") else 1e10): + if child.child1 is not None: + name += name_object(child) + else: + name += str(child.id) + name += " " + name += "}" + return name + +def name_pair(comp1, comp2): + return '('+str(comp1.id)+','+str(comp2.id)+')' + +known_roots = {} +def assign_id_to_root(tree): + + # Determine the object's description, then search to see if we + # know about it. If we do, return that ID, otherwise create a new + # ID. + + global known_roots + my_name = name_object(tree) + if my_name in known_roots.keys(): + return known_roots[my_name] + else: + new_root_id = new_root_index() + known_roots[my_name] = new_root_id + return new_root_id + +def is_a_parent(child1_key, child2_key): + return child1_key > 0 or child2_key > 0 + +def is_not_a_child(is_a_child): + return is_a_child == 0 + +def get_component_binary_elements(comp1, comp2, kep, peri = 0): + + 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 + if peri: + M,th = kep.get_angles() + if M < 0: + kep.advance_to_periastron() + else: + kep.return_to_periastron() + t = kep.get_time() + + return mass,a,e,r,E,t + +def get_cm_binary_elements(p, kep, peri = 0): + return get_component_binary_elements(p.child1, p.child2, kep, peri) + +class DidNotFinishException(Exception): + pass + +class Multiples(object): + + def __init__(self, + gravity_code, + resolve_collision_code_creation_function, + kepler_code, + gravity_constant = None, **options): + + # Codes to use. + + self.gravity_code = gravity_code + self.resolve_collision_code_creation_function \ + = resolve_collision_code_creation_function + self.kepler = kepler_code + + # Data structures. + + # Local copy of CM (= root) particles in the gravity code. + self._inmemory_particles = self.gravity_code.particles.copy() + + # Dictionary connecting center of mass particles with the + # multiple tree structure lying under them. + # + # Syntax: + # root_to_tree[center_of_mass] = binary_tree + # + # where center_of_mass is a Particle and binary_tree is created by + # trees.BinaryTreesOnAParticleSet. + + self.root_to_tree = {} + + ''' + # Unecessary code since copy_attribute below does the same thing. + if len(self.gravity_code.particles) == 0: + self._inmemory_particles.id = None + else: + self._inmemory_particles.id = self.gravity_code.particles.index_in_code + ''' + + self._inmemory_particles.child1 = None + self._inmemory_particles.child2 = None + self.channel_from_code_to_memory = \ + self.gravity_code.particles.new_channel_to(self._inmemory_particles) + self.channel_from_code_to_memory.copy_attribute("index_in_code", "id") + + # FLASH interface needs a channel the other way also - Josh. + self.channel_from_memory_to_code = \ + self._inmemory_particles.new_channel_to(self.gravity_code.particles) + + if gravity_constant is None: # default is N-body units + gravity_constant = nbody_system.G + + self.gravity_constant = gravity_constant + + # Energy bookkeeping. + zero_energy = zero * self.gravity_code.kinetic_energy + self.multiples_external_tidal_correction = zero_energy + self.multiples_internal_tidal_correction = zero_energy + self.multiples_integration_energy_error = zero_energy + + # Count the number of collisions we found for comparing with + # encounters algorithm. + self.number_of_collisions = 0 + + # Repeat encounter management data + self.old_star_1 = 0 + self.old_star_2 = 0 + self.repeat_count = 0 + + # The following tunable parameters govern the multiples logic: + + # Nominal size of the top-level encounter, relative to the sum + # of the radii of the interacting components (no veto) or + # their separation (veto). + + #self.initial_scale_factor = 1.0 + self.initial_scale_factor = 2.0 + + # Perturbation above which to include a neighbor, estimated + # using the neighbor distance and the initial separation of + # the top-level two-body encounter. (Previously was a simple + # distance criterion...) + + #self.neighbor_distance_factor = 1.0 + #self.neighbor_distance_factor = 2.0 + self.neighbor_perturbation_limit = 0.02 + + # Neighbor veto policy. True means we allow neighbors to veto + # a two-body encounter (meaning that don't want to deal with + # complex initial many-body configurations). False means we + # include neighbors in the multiple integration. + + #self.neighbor_veto = False + self.neighbor_veto = True + + # Size of the rescaled final system, relative to the initial + # scale. Should be 1 + epsilon. + + self.final_scale_factor = 1.01 + + # Initial separation for the scattering experiment, relative + # to the initial scale. May be limited by apocenter in case + # of a bound top-level interaction. + + self.initial_scatter_factor = 10.0 + + # Final separation for the scattering experiment, relative to + # the initial scattering scale (meaning 2 x 10 times the + # initial encounter scale, by default). May be limited by + # binary properties in case of a bound top-level interaction. + + #self.final_scatter_factor = 10.0 + self.final_scatter_factor = 2.0 + + # Binary retention policy. Retain a binary if its apocenter + # (True) or 2*semi-major axis (False) is less than the + # dynamical radius of its CM. False is the more conservative + # choice. + + self.retain_binary_apocenter = True + + # Maximum allowed perturbation at apocenter on a wide binary. + self.wide_perturbation_limit = 0.01 + + # Turn on/off global debugging output level (0 = no output, 1 + # = minimal, 2 = normal debug, 3 = verbose debug). + + self.global_debug = 1 + + # Turn on debugging in the encounters code. + self.debug_encounters = False + + # Turn on/off experimental code to check tidal perturbation. + self.check_tidal_perturbation = False + + # Store Callback Function to be executed on a Zero-Step Encounter + # Restored from Tycho's Version of Multiples + self.callback = None + + # The encounterLogger is used to store information about an encounter + # before said encounter is processed by the Multiples module. + # It has a similar function to the callback property above and is a + # function with the format of: encounterLogger(time, particles_in_encounter) + # Added by Joe Glaser, 8/12/20 + self.encounterLogger = None + + @property + def particles(self): + return self.gravity_code.particles + + @property + def total_mass(self): + return self.gravity_code.total_mass + + @property + def kinetic_energy(self): + return self.gravity_code.kinetic_energy + + @property + def potential_energy(self): + return self.gravity_code.potential_energy + + @property + def parameters(self): + return self.gravity_code.parameters + + @property + def model_time(self): + return self.gravity_code.model_time + + @property + def stars(self): + result = self._inmemory_particles.copy() + for root, tree in self.root_to_tree.items(): + root_particle = root.as_particle_in_set(self._inmemory_particles) + result.remove_particle(root) + + # This returns a pointer to the actual leaves - Josh. + + leaves = tree.get_leafs_subset() + + original_star = tree.particle + + dx = root_particle.x - original_star.x + dy = root_particle.y - original_star.y + dz = root_particle.z - original_star.z + dvx = root_particle.vx - original_star.vx + dvy = root_particle.vy - original_star.vy + dvz = root_particle.vz - original_star.vz + + # Note that here we add leaves to another particle set, + # and this becomes its own deep copy of leaves. So + # changes to leaves_in_result have no effect on leaves - + # Josh. + + leaves_in_result = result.add_particles(leaves) + leaves_in_result.x += dx + leaves_in_result.y += dy + leaves_in_result.z += dz + leaves_in_result.vx += dvx + leaves_in_result.vy += dvy + leaves_in_result.vz += dvz + + return result + + def update_leaves_pos_vel(self): + + # The FLASH interface needs a function that updates the + # properties of the leaves from the properties of the + # particles in the gravity code - Josh. + + # NOTE: Unlike multiples.stars, this actually moves the real + # leaves, not a copy of the leaf particles. So tree.particle + # also then needs to be updated - Josh. + + local_debug = False + self.channel_from_code_to_memory.copy() # update the copy in memory + # from the gravity code - Josh + + for root, tree in self.root_to_tree.items(): + root_particle = root.as_particle_in_set(self._inmemory_particles) + + leaves = tree.get_leafs_subset() + original_star = tree.particle + + if (local_debug): + old_leaves_x = leaves.x + + print("In update_leaves_pos_vel before update.") + print("Tree pos =", tree.particle.position.in_(units.cm)) + print("Root pos =", root.position.in_(units.cm)) + print("Leaf pos =", leaves.position.in_(units.cm)) + + dx = root_particle.x - original_star.x + dy = root_particle.y - original_star.y + dz = root_particle.z - original_star.z + dvx = root_particle.vx - original_star.vx + dvy = root_particle.vy - original_star.vy + dvz = root_particle.vz - original_star.vz + + leaves.x += dx + leaves.y += dy + leaves.z += dz + leaves.vx += dvx + leaves.vy += dvy + leaves.vz += dvz + + # Update the original particle info stored in + # tree.particle - Josh. + + original_star.x = root_particle.x + original_star.y = root_particle.y + original_star.z = root_particle.z + + original_star.vx = root_particle.vx + original_star.vy = root_particle.vy + original_star.vz = root_particle.vz + + if (local_debug): + new_leaves = tree.get_leafs_subset() + leaves_dx = leaves.x - old_leaves_x + + if (leaves_dx[0].number == 0.0): + print("These leaves aren't moving!") + elif (leaves_dx[0].number == dx[0].number): + print("These leaves arrived precisely when they meant to!") + else: + print("I have no idea what these damn leaves are doing!") + print("leaves_dx =", leaves_dx) + print("dx =", dx) + + if (local_debug): + print("In update_leaves_pos_vel after update.") + print("Tree pos =", tree.particle.position.in_(units.cm)) + print("Root pos =", root.position.in_(units.cm)) + print("Leaf pos =", leaves.position.in_(units.cm)) + + return + + def create_binary(self, star1, star2): + + # Experimental code to include a binary directly into the + # multiples database. + + M,a,e,r,E,tperi = get_component_binary_elements(star1, star2, + self.kepler, 1) + + binary = Particles() + binary.add_particle(star1) + binary.add_particle(star2) + + cm = Particle(mass=M, + position=binary.center_of_mass(), + velocity=binary.center_of_mass_velocity()) + binary.add_particle(cm) + + binary.child1 = None + binary.child2 = None + cm = binary[2] + cm.child1 = binary[0] + cm.child2 = binary[1] + + set_radii(binary, self.kepler, self.global_debug) + + self.gravity_code.particles.remove_particle(star1) + self.gravity_code.particles.remove_particle(star2) + + # Complete the bookkeeping. + + tree = trees.BinaryTreesOnAParticleSet(binary, + "child1", "child2") + + for t in tree.iter_binary_trees(): # only one tree... + t.particle.id = assign_id_to_root(t) + self.gravity_code.particles.add_particle(t.particle) + self.root_to_tree[t.particle] = t.copy() + print('\nCreated binary from', star1.id, 'and', star2.id, \ + ' CM =', t.particle.id) + print('M =', M, ' a =', a, ' e =', e, ' E =', E) + + self.gravity_code.particles.synchronize_to(self._inmemory_particles) + self.channel_from_code_to_memory.copy_attribute("index_in_code", "id") + + def check_trees(self): + + # Print out some debugging information on multiples in the system. + + print('') + print('check_trees:', len(self.root_to_tree), 'tree(s)') + for root, tree in self.root_to_tree.items(): + print(root.position) # current + print(tree.particle.position) # original + leaves = tree.get_leafs_subset() # components (original) + print(leaves.center_of_mass()) + print('') + + def get_gravity_at_point(self, radius, x, y, z): + return self.gravity_code.get_gravity_at_point(radius, x, y, z) + + def get_potential_at_point(self, radius, x, y, z): + return self.gravity_code.get_potential_at_point(radius, x, y, z) + + def commit_particles(self): + return self.gravity_code.commit_particles() + + def get_time(self): + return self.gravity_code.get_time() + + def get_total_energy(self, code): + try: + binaries_energy = code.get_binary_energy() # include binaries if + except: # the code understands + binaries_energy = zero + total_energy = code.potential_energy + code.kinetic_energy \ + + binaries_energy + + return total_energy + + #-------------------------------------------------------------- + # Note that the true total energy of a multiple isn't quite the + # Emul returned below, since the tidal potential of components on + # one another is not taken into account. + + def get_total_multiple_energy(self): # uses kepler + Nbin = 0 + Nmul = 0 + Emul = zero + for x in self.root_to_tree.values(): # loop over top-level trees + Nmul += 1 + nb,E = get_multiple_energy(x, self.kepler) + Nbin += nb + Emul += E + return Nmul, Nbin, Emul + + # This version returns the true total energy of all multiples. + + def get_total_multiple_energy2(self): + Nbin = 0 + Nmul = 0 + Emul = zero + for x in self.root_to_tree.values(): # loop over top-level trees + Nmul += 1 + nb,E = get_multiple_energy2(x, self.gravity_constant) + Nbin += nb + Emul += E + return Nmul, Nbin, Emul + + def print_multiples(self): # uses kepler + + # Print basic information on all multiples in the system, + # using the root_to_tree database. This version uses + # print_multiple_simple() to format the output. + + if self.global_debug > 0: + for x in self.root_to_tree.values(): + print_multiple_simple(x, self.kepler) + + def print_multiples2(self, pre, kT, dcen): # uses kepler + + # Print information on all multiples in the system, using the + # root_to_tree database. This version uses + # print_multiple_detailed() to format the output, and returns + # the numbers and energies of multiples found. + + Nbin = 0 + Nmul = 0 + Emul = zero + for x in self.root_to_tree.values(): + Nmul += 1 + nb,E = print_multiple_detailed(x, self.kepler, pre, kT, dcen) + Nbin += nb + Emul += E + return Nmul, Nbin, Emul + + def print_trees_summary(self): + if len(self.root_to_tree) > 0: + print('number of multiples:', len(self.root_to_tree)) + sys.stdout.flush() + + def evolve_model(self, end_time, **kwargs): + callback = kwargs.get("callback", self.callback) + stopping_condition = \ + self.gravity_code.stopping_conditions.collision_detection + #stopping_condition.enable() # allow user to set this; don't override + + time = self.gravity_code.model_time + print("\nmultiples: evolve model to", end_time, "starting at", time) + sys.stdout.flush() + + count_resolve_encounter = 0 + count_ignore_encounter = 0 + + while time <= end_time: # the <= here allows zero-length steps + + if self.global_debug > 1: + print('') + print('calling evolve_model from', \ + self.gravity_code.model_time, 'to', end_time) + sys.stdout.flush() + + self.gravity_code.evolve_model(end_time) + newtime = self.gravity_code.model_time + + # JB modified this: in Bonsai we can take a zero-length + # time step to detect multiples. That would cause the + # newtime == time to evaluate to true when there are + # multiples detected and break out of the evaluate loop + # before the time reached end_time. Same is now possible + # with ph4 (SLWM, 6/18). + + if newtime == time and (stopping_condition.is_set() == False): + break + + time = newtime + + if stopping_condition.is_set(): + + # An encounter has occurred. Synchronize all stars in + # the gravity code. We synchronize everything for + # now, but it would be better to just synchronize + # neighbors if gravity_code supports that. TODO + + self.gravity_code.synchronize_model() + + star1 = stopping_condition.particles(0)[0] + star2 = stopping_condition.particles(1)[0] + ignore = 0 + self.before = Particles() + self.after = Particles() + self.after_smalln = Particles() + #print 'self.before:', self.before + + # Note from Steve, 8/12: We can pick up a lot of + # encounters that are then ignored here. I have + # (temporarily?) duplicated this check in the ph4 + # module (jdata.cc). + + r = (star2.position-star1.position).length() + v = (star2.velocity-star1.velocity).length() + vr = ((star2.velocity-star1.velocity) \ + * (star2.position-star1.position)).sum() + + EPS = 0.001 + if True or vr < EPS*r*v: # True ==> keep all encounters + # returned by gravity_code + + if self.global_debug > 1: + print('\n'+'~'*60) + elif self.global_debug > 0: + print('') + if self.global_debug > 0: + print('interaction at time', time) + + # As with synchronize above, we should only copy + # over data for the interacting particles and + # their neighbors. TODO + + self.channel_from_code_to_memory.copy() + self.channel_from_code_to_memory.copy_attribute("index_in_code", "id") + + initial_energy = self.get_total_energy(self.gravity_code) + + star1 = star1.as_particle_in_set(self._inmemory_particles) + star2 = star2.as_particle_in_set(self._inmemory_particles) + cont = True + + if self.global_debug > 0: + print('initial top-level:', \ + star1.id, '('+str(star1.radius)+')', \ + star2.id, '('+str(star2.radius)+')') + if self.global_debug > 1: + print(' r =', r) + print(' v =', v) + print(' v.r =', vr) + sys.stdout.flush() + + # Change Made by Joe Glaser 08/08/2020 + # This moves the callback function to not be called if + # an encounter if it is not in zero-timestep mode. + zero_mode = self.gravity_code.parameters.zero_step_mode + print("Currently Set ZeroStep Mode:", zero_mode) + if callback != None and zero_mode==0: + print("Initiating Callback Function ...") + cont = callback(time, star1, star2) + + veto, dE_top_level_scatter, dphi_top, dE_mul, \ + dphi_int, dE_int, final_particles \ + = self.manage_encounter(time, star1, star2, + self._inmemory_particles, + self.gravity_code.particles, + self.kepler) + + if cont and not veto: + # Recommit is done automatically and reinitializes all + # particles. Later we will just reinitialize a list if + # gravity_code supports it. TODO + + self.gravity_code.particles.synchronize_to( + self._inmemory_particles) # sets _inmemory = gravity + self.channel_from_code_to_memory.copy_attribute( + "index_in_code", "id") + + final_energy = self.get_total_energy(self.gravity_code) + dE_top_level = final_energy - initial_energy + + # Local bookkeeping: + # + # dE_top_level is the actual energy + # change in the top-level gravity system + # due to this encounter + # + # dE_top_level_scatter is the change in + # top-level internal energy of the + # scattering system + # + # dphi_top is the top-level tidal error + # (currently unabsorbed) due to the + # change in configuration of the + # scattering system in the top-level + # tidal field + # + # dE_mul is the change in stored + # multiple energy associated with the + # encounter + # + # dphi_int is the internal tidal energy + # error due to configuration changes in + # the scattering system + # + # dE_int is the integration error in the + # scattering calculation + # + # We *always* expect + # + # dE_top_level - dE_top_level_scatter - dphi_top = 0. + # + # If this is not the case, then there is an + # error in the internal bookkeeping of + # manage_encounter(). + + if self.global_debug > 2: + #print 'top-level initial energy =', initial_energy + #print 'top-level final energy =', final_energy + print('dE_top_level =', dE_top_level) + print('dE_top_level_scatter =', dE_top_level_scatter) + print('dphi_top =', dphi_top) + print('dphi_int =', dphi_int) + print('dE_int =', dE_int) + print('dE_top_level-dE_top_level_scatter-dphi_top =',\ + dE_top_level - dE_top_level_scatter - dphi_top) + + if self.global_debug > 2: + print('net local error =', \ + dE_top_level - dE_top_level_scatter - dphi_top) + print('scatter integration error =', dE_int) + + # We also expect + # + # dE_top_level_scatter + dE_mul + # = dphi_top + dE_int - dphi_int. + # + # Monitor this and keep track of the + # cumulative value of the right-hand side of + # this equation. + + if self.global_debug > 2: + print('dE_mul =', dE_mul) + print('internal local error =', \ + dE_top_level + dE_mul - dphi_top) + print('corrected internal local error =', \ + dE_top_level + dE_mul - dphi_top \ + + dphi_int - dE_int) + + self.multiples_external_tidal_correction += dphi_top + self.multiples_internal_tidal_correction -= dphi_int + self.multiples_integration_energy_error += dE_int + + # Doing this energy calculation at every + # encounter is expensive when dealing with + # hundreds of binaries or more. It is clearly + # problematic when building a whole system of + # binaries. + + #Nmul, Nbin, Emul = self.get_total_multiple_energy2() + + # Global bookkeeping: + # + # We don't absorb individual tidal or + # integration errors, but instead store their + # totals in + # + # self.multiples_external_tidal_correction, + # self.multiples_internal_tidal_correction, + # and + # self.multiples_inegration_energy_error. + # + # Then + # + # E(top-level) + Emul + # - self.multiples_external_tidal_correction + # - self.multiples_internal_tidal_correction + # - self.multiples_integration_energy_error + # + # should be constant. Any non-conservation + # represents an error in bookkeeping or + # algorithm design. + ''' + print 'total energy (top+mul) =', \ + final_energy + Emul + print 'corrected total energy =', \ + final_energy + Emul \ + - self.multiples_external_tidal_correction \ + - self.multiples_internal_tidal_correction \ + - self.multiples_integration_energy_error + ''' + + # Print info on all multiples associated with + # the current interaction. + + if self.global_debug > 1: + for x in final_particles: + if hasattr(x, "child1") \ + and not (getattr(x, "child1") is None): + print_multiple_simple( + trees.BinaryTreeOnParticle(x), + self.kepler) + + count_resolve_encounter += 1 + + else: + ignore = 1 + + if self.global_debug > 1: + print('~'*60) + sys.stdout.flush() + + else: + ignore = 1 + + self.number_of_collisions += 1 + + ''' + io.write_set_to_file((self.before, + self.after, self.after_smalln), + "multiples-{0}.h5".format(self.number_of_collisions), + "amuse", names=('before', 'after', 'after_smalln'), + version="2.0", append_to_file=False) + ''' + count_ignore_encounter += ignore + + print('') + print('Resolved', count_resolve_encounter, 'encounters') + print('Ignored', count_ignore_encounter, 'encounters') + sys.stdout.flush() + + self.gravity_code.synchronize_model() + self.channel_from_code_to_memory.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. + + self.channel_from_code_to_memory.copy_attribute("index_in_code", "id") + + def expand_encounter(self, scattering_stars, delete=True): + + # Create an encounter particle set from the top-level stars. + # Add stars to the encounter set, add in components when we + # encounter a binary/multiple. + + # Added in delete keyword (default is True which is the same + # as before. This allows for use in Tycho's callback function. + # Added by Joe Glaser on 8/11/20 + + particles_in_encounter = Particles(0) + Emul = zero + + for star in scattering_stars: + if star in self.root_to_tree: + tree = self.root_to_tree[star] + isbin, dEmul = get_multiple_energy2(tree, self.gravity_constant) + Emul += dEmul + openup_tree(star, tree, particles_in_encounter) + if delete: del self.root_to_tree[star] + else: + particles_in_encounter.add_particle(star) + + return particles_in_encounter, Emul + + def manage_encounter(self, global_time, star1, star2, + stars, gravity_stars, kep): + + # Manage an encounter between star1 and star2. Stars is the + # python memory dataset (_inmemory_particles). Gravity_stars + # is the gravity code data (only used to remove the old + # components and add new ones). On entry, stars and + # gravity_stars should contain the same information. Return + # values are the change in top-level energy, the tidal error, + # and the integration error in the scattering calculation. + # Steps below follow those defined in the PDF description. + + # print 'in manage_encounter' + # sys.stdout.flush() + + # Record the state of the system prior to the encounter, in + # case we need to abort and return without making any changes. + # + # 'gravity_stars' is no longer included in the snapshot below, + # as it would make N individual calls to get_state... + + snapshot = { + 'global_time': global_time, + 'star1': star1.copy(), + 'star2': star2.copy(), + 'stars': stars.copy(), + #'gravity_stars': gravity_stars.copy(), + 'self.root_to_tree': self.root_to_tree.copy(), + 'particles_in_encounter': Particles(0), + 'scattering_stars': Particles(0) + } + snapshot['particles_in_encounter'].add_particle(snapshot['star1']) + snapshot['particles_in_encounter'].add_particle(snapshot['star2']) + + # find_binaries(stars, self.gravity_constant) + + # self.check_trees() + + #---------------------------------------------------------------- + # 1a. Build a list of stars involved in the scattering. Start + # with star1 and star2. + + scattering_stars = Particles(particles = (star1, star2)) + star1 = scattering_stars[0] + star2 = scattering_stars[1] + center_of_mass = scattering_stars.center_of_mass() + other_stars = stars - scattering_stars # probably only need perturbers? + + # Brewer Mod: Check for a repeat encounter. + + if (star1.id == self.old_star_1 and star2.id == self.old_star_2) \ + or (star1.id == self.old_star_2 and star2.id == self.old_star_1): + self.repeat_count += 1 + else: + self.repeat_count = 0 + self.old_star_1 = star1.id + self.old_star_2 = star2.id + + # 1b. Add neighbors if desired. Use a perturbation criterion. + # Also impose a simple neighbor veto, if specified. Start by + # sorting all stars by perturbation on the CM. Later, use + # neighbors only, if supported. TODO + + sep12 = ((star1.position-star2.position)**2).sum().sqrt() + rad12 = star1.radius + star2.radius + + # Sep12 is the separation of the two original components. It + # should be slightly less than the sum of their radii, rad12, + # but it may be much less in unexpected circumstances or if + # vetoing is in effect. Initial_scale sets the "size" of the + # interaction and the distance to which the final products + # will be rescaled. Rad12 also ~ the 90 degree scattering + # distance for two stars, and hence the natural limit on + # binary scale. + + if not self.neighbor_veto: + initial_scale = self.initial_scale_factor * rad12 + else: + initial_scale = self.initial_scale_factor * sep12 + + if self.global_debug > 1: + print('initial_scale =', initial_scale) + + # The basic sort on other_stars is by perturbation, not + # distance. Maintain sorted lists of stars, distances (d), + # and perturbations (actually m/d**3). + + distances = (other_stars.position - center_of_mass).lengths() + pert = other_stars.mass / distances**3 + indices = numpy.argsort(-pert.number) # decreasing sort + sorted_stars = other_stars[indices] + sorted_distances = distances[indices] + sorted_perturbations = pert[indices] + fac12 = 0.5*(star1.mass + star2.mass)/sep12**3 + + largest_perturbers = [] + if self.check_tidal_perturbation and len(sorted_stars) > 0: + + if self.global_debug > 1: + print("sorted_stars", sorted_stars[:5]) + print("sorted_distances", sorted_distances[:5]) + print("sorted_perturbations", sorted_perturbations[:5]/fac12) + + max_pert = sorted_perturbations[0]/fac12 + largest_perturbers = [sorted_stars[0]] + + # This should be replaced with something faster using + # numpy, like: + # + # largest_perturbers = sorted_stars[np.greater(sorted_perturbations, + # 0.025*sorted_perturbations[0])] - Josh. + + for i in range(1, len(sorted_stars)): + if sorted_perturbations[i] > 0.025*sorted_perturbations[0]: + largest_perturbers.append(sorted_stars[i]) + + # Perturbation limit for identification as a neighbor. + + pert_min = self.neighbor_perturbation_limit*fac12 + for i in range(len(sorted_stars)): # NB no loop if len() = 0 + + star = sorted_stars[i] + + # Include anything lying "inside" the binary, even if it + # is a weak perturber. + + if sorted_perturbations[i] > pert_min \ + or sorted_distances[i] < sep12: + if not self.neighbor_veto: + scattering_stars.add_particle(star) + if self.global_debug > 1: + print('added', end=' ') + if hasattr(star, 'id'): + print('star', star.id, end=' ') + else: + print('unknown star', end=' ') + print('to scattering list') + sys.stdout.flush() + snapshot['scattering_stars'].add_particle(star) + #initial_scale = sorted_distances[i] # don't expand! + else: + if self.global_debug > 0: + print('encounter vetoed by', \ + star.id, 'at distance', \ + sorted_distances[i], \ + 'pert =', sorted_perturbations[i]/fac12) + if self.repeat_count > 0: self.repeat_count -= 1 + return True, 0., 0., 0., 0., 0., None + + self.before.add_particles(scattering_stars) + + # Note: sorted_stars, etc. are used once more, when checking + # for wide binaries (at 6b below). + + #---------------------------------------------------------------- + # 2a. Calculate the total internal and external potential + # energy of stars to remove from the gravity system, using the + # potential of scattering_stars relative to the other + # top-level objects in the stars list (later: just use + # neighbors TODO). + + # Terminology from the PDF description: + + E0 = scattering_stars.kinetic_energy() \ + + scattering_stars.potential_energy(G=self.gravity_constant) + phi_rem = potential_energy_in_field(scattering_stars, + stars-scattering_stars, + G=self.gravity_constant) + + if self.global_debug > 2: + print('E0 =', E0) + print('phi_rem =', phi_rem) + + # 2b. If there are no neighbors, separate star1 and star2 to + # some larger "scattering" radius. If neighbors exist, + # just start the "scattering" interaction in place. + + # First define some basic properties of the top-level + # interaction. + + M,a,e,r,E,tperi = get_component_binary_elements(star1, star2, + self.kepler, 1) + + Etop = E*star1.mass*star2.mass/M + ttrans = self.gravity_constant*M/(4*abs(E))**1.5 + + # Note: transit time = 0.056 * period for a bound orbit. + + if e < 1: + peri = a*(1-e) + apo = a*(1+e) + period = self.kepler.get_period() + else: + peri = a*(e-1) + apo = 1.e9*a # 1.e9 is large but otherwise arbitrary + period = 1.e9*ttrans + + initial_scatter_scale = self.initial_scatter_factor * initial_scale + + # Limit initial_scatter_scale (rescale_binary_components will + # impose a limit, but good to have the limit available at this + # level). + + if initial_scatter_scale > 0.9*apo: + initial_scatter_scale = 0.9*apo + + if len(scattering_stars) == 2: + #print "rescaling in:", (star1.position - star2.position).length() + rescale_binary_components(star1, star2, kep, + initial_scatter_scale, compress=False) + #print "rescaling out:", (star1.position - star2.position).length() + + # 2c. Remove the interacting stars from the gravity module. + + for s in scattering_stars: + gravity_stars.remove_particle(s) + + #---------------------------------------------------------------- + # 3a. Create a particle set to perform the close encounter + # calculation. + + # Note this has to delete the root_to_tree particle in + # multiples as we have no idea what the end product of the + # encounter will be. So deleting in expand_encounter really + # is a feature, not a bug. Don't mess with it! - Josh + + particles_in_encounter, Emul_init \ + = self.expand_encounter(scattering_stars) + # Terminology from the PDF description: + + E1 = particles_in_encounter.kinetic_energy() + \ + particles_in_encounter.potential_energy(G=self.gravity_constant) + + dphi_1 = E1 - E0 - Emul_init + + if self.global_debug > 2: + print('E1 =', E1) + print('Emul_init =', Emul_init) + print('dphi_1 =', dphi_1) + + #---------------------------------------------------------------- + # 3b. Log the Encounter with the user provided encounterLogger. + # Only runs this if the gravity_code is NOT in zero_step_mode. + # Added by Joe Glaser on 8/12/20 + zeromode = self.gravity_code.parameters.zero_step_mode + if self.encounterLogger != None and zeromode == 0: + self.encounterLogger(global_time, particles_in_encounter) + + #---------------------------------------------------------------- + # 4. Run the small-N encounter in the center of mass frame. + + total_mass = scattering_stars.mass.sum() + + cmpos = scattering_stars.center_of_mass() + cmvel = scattering_stars.center_of_mass_velocity() + particles_in_encounter.position -= cmpos + particles_in_encounter.velocity -= cmvel + + #E1CM = particles_in_encounter.kinetic_energy() + \ + # particles_in_encounter.potential_energy(G=self.gravity_constant) + #print 'E1 (CM) =', E1CM + + # Relevant available length and time scales: + # + # Encounter: + # sep12 = actual separation + # rad12 = sum of radii (should be ~b90) + # + # Top-level orbit: + # a = orbital semimajor axis + # peri = orbital periastronn + # apo = orbital apastron + # tperi = time to pericenter + # period = orbital period, if defined + # ttrans = transit time + # + # Resonance: + # rvir = viral length scale + # tvir = virial time scale + + rvir = self.gravity_constant*M/(4*abs(E1/M)) + tvir = self.gravity_constant*M/(4*abs(E1/M))**1.5 + + if self.global_debug > 2: + print('Encounter:') + print(' sep12 =', sep12) + print(' rad12 =', rad12) + print('Top-level:') + print(' E/mu =', E) + print(' Etop =', Etop) + print(' M =', M) + print(' semi =', a) + print(' ecc =', e) + print(' peri =', peri) + print(' apo =', apo) + print(' tperi =', tperi) + print(' ttrans =', ttrans) + print(' period =', period) + print('Resonance:') + print(' rvir =', rvir) + print(' tvir =', tvir) + else: + if self.global_debug > 0: + print('M =', M, ' Etop =', Etop) + if self.global_debug > 1: + print('a =', a, ' e =', e, ' P =', period) + + sys.stdout.flush() + + # The original concept of this module was to follow the + # encounter as an isolated scattering experiment until it is + # cleanly resolved. In this case, the bookkeeping and + # post-encounter logic are straightforward. We expect that a + # clean resolution always eventually occurs, but for a complex + # interaction this may take a long time. In addition, the + # long time scales and large excursions of the intermediate + # orbit may render the physicality of the scattering approach + # questionable. + + # The alternative approach pursued below is to try to define + # limiting length and time scales for the encounter, based on + # the initial configuration. Encounters exceeding these limits + # will be returned to the large-N simulation, possibly to be + # picked up again later. This approach leads to significant + # bookkeeping issues, and the "clean" original concept should + # always be retained as a fallback option. + + # We need a reliable time scale to set end_time and delta_t + # for the scattering interaction. It is possible that we pick + # up an encounter very close to periastron, so tperi may not + # be useful. With reasonable limits on encounter size and a + # treatment of quasi-stable systems, a time limit on the + # smallN integration may not be needed, but impose some + # reasonable value here, just in case. Note that we have to + # deal with the possible consequences in resolve_collision(). + + # Note that the current check for quasistability requires that + # the system configuration remain unchanged for 10 outer + # orbital periods, and is not yet reliable. TODO + + # If the encounter is a flyby, then the relevant scales are + # the orbital semimajor axis and transit time (*10, say). We + # don't want to follow a wide bound system onto a second orbit + # unless the size of the orbit is less than a few times the 90 + # degree turnaround distance. If the encounter is a + # resonance, then the relative scales are the virial radius + # (*10) and virial time scale (*100). If it is bound but wide + # (and likely a flyby), then the relevant scales are the the + # orbital semimajor axis and period (*10, say). + + # Also set a limit on the minimum scale, in case of retries. + + end_time = max(2*abs(tperi), 10*ttrans, 100*tvir) + if E.number < 0: end_time = max(end_time, 10*period) + + delta_t = max(1.5*abs(tperi), tvir) + + if self.global_debug > 1: + print('end_time =', end_time) + print('delta_t =', delta_t) + + # Note: radii used here should really be based on + # perturbation, not simply distance. TODO + + orbit_scale = 2*a + if E.number < 0: orbit_scale = 1.1*a*(1+e) # a*(1+0.9*e) + + if self.global_debug > 2: + print('orbit_scale =', orbit_scale) + + # Final_scatter_scale is the scale at which we will terminate + # the smallN integration. This is a guess of the scale where, + # if the system exceeds it, the interacting particles can be + # decomposed into well separated pieces that can be returned + # to the N=body code, even if the encounter isn't over. + + final_scatter_scale \ + = max(self.final_scatter_factor * initial_scatter_scale, + orbit_scale, 10*rvir) + + # Limit the scatter scale in case of a very wide orbit. + + if orbit_scale > 2*initial_scatter_scale \ + and final_scatter_scale > orbit_scale: + final_scatter_scale = orbit_scale + + min_scatter_scale = 2*initial_scale # never go below this value + if min_scatter_scale >= 0.5*final_scatter_scale: + final_scatter_scale = 2*min_scatter_scale + + # The integration ends when any particle is more than + # final_scatter_scale from the CM of the system (hence the + # factor of 2). RECONSIDER - needs a mass scale factor, and + # still OK for a wide orbit? TODO + + final_scatter_scale /= 2 + min_scatter_scale /= 2 + + if self.global_debug > 1: + print('final_scatter_scale =', final_scatter_scale) + print('min_scatter_scale =', min_scatter_scale) + + # NOTE: to revert to the original concept, simply set + # final_scatter_scale and end_time to very large values. + + if 0: + print('particles in encounter:') + print('position:', particles_in_encounter.position) + print('velocity:', particles_in_encounter.velocity) + + try: + scatter_energy_error \ + = self.resolve_collision(particles_in_encounter, + final_scatter_scale, + min_scatter_scale, + end_time, delta_t) + + except DidNotFinishException: + + # In this case, simply abort the encounter and continue + # the main simulation. + + print("*** SmallN encounter did not finish. ", \ + "Aborting and returning to top level.") + + global_time = snapshot['global_time'] + star1 = snapshot['star1'] + star2 = snapshot['star2'] + stars = snapshot['stars'] + #gravity_stars = snapshot['gravity_stars'] + gravity_stars.add_particle(star1) + gravity_stars.add_particle(star2) + gravity_stars.add_particles(snapshot['scattering_stars']) + self.root_to_tree = snapshot['self.root_to_tree'] + zero_en = 0.0 * E0 + + return False, zero_en, zero_en, zero_en, zero_en, zero_en, \ + snapshot['particles_in_encounter'] + + # Note that on return, particles_in_encounter contains CM + # nodes in the list. + + E2CM = get_energy_of_leaves(particles_in_encounter, + G=self.gravity_constant) + Etop = particles_in_encounter.kinetic_energy() \ + + particles_in_encounter.potential_energy(G=self.gravity_constant) + if self.global_debug > 1: + print('E2 (CM) =', E2CM) + + particles_in_encounter.position += cmpos + particles_in_encounter.velocity += cmvel + + # Terminology from the PDF description: + + E2 = get_energy_of_leaves(particles_in_encounter, + G=self.gravity_constant) + dE_int = E2 - E1 # should equal scatter_energy_error + err = (dE_int-scatter_energy_error)/max(E1,E2) + if abs(err) > 1.e-12: + if self.global_debug > 0: + print('*** warning: dE_int mismatch ***') + if self.global_debug > 1: + print('scatter_energy_error =', scatter_energy_error) + print('dE_int =', dE_int) + #print particles_in_encounter + print('E1 =', E1, 'E2 =', E2) + + if self.global_debug > 2: + print('E2 =', E2) + print('scatter_energy_error =', scatter_energy_error) + print('dE_int =', dE_int) + + #---------------------------------------------------------------- + # 5a. Identify multiple structure after the encounter. First + # create an object to handle the new binary information. + + #Brewer Mod: Create the appropriate COM particle for the pseudo-binary + ''' + if self.repeat_count > 9: + print "repeat encounter detected; forcing binary creation" + pseudoCOM = Particles(1) + pseudoCOM.child1 = star1 + pseudoCOM.child2 = star2 + pseudoCOM.mass = star1.mass + star2.mass + pseudoCOM.position = cmpos + pseudoCOM.velocity = cmvel + pseudoCOM.radius = star1.radius + star2.radius + print particles_in_encounter + print pseudoCOM + particles_in_encounter.add_particles_in_store(pseudoCOM) + ''' + #End Mod section. + + binaries = trees.BinaryTreesOnAParticleSet(particles_in_encounter, + "child1", "child2") + + # 5b. Compress the top-level nodes before adding them to the + # gravity code. Also recompute the external potential and + # optionally absorb the tidal error into the top-level + # nodes of the encounter list. Finally, add the change in + # top-level energy of the interacting subset into dEmult, + # so E(ph4) + dEmult should be conserved. + + # Single stars. + stars_not_in_a_multiple = binaries.particles_not_in_a_multiple() + + #print 'stars_not_in_a_multiple:' + #print stars_not_in_a_multiple + + # Multiple centers of mass. + roots_of_trees = binaries.roots() + + #---------------------------------------------------------------- + # 6a. Scale to a radius slightly larger than the initial one. + # Rescaling does just that -- neither computes nor attempts to + # absorb the tidal error. If we want to absorb the tidal + # error rather than simply recording it, do so after splitting + # wide binaries below. TODO + + final_scale = self.final_scale_factor * initial_scale + + # Note that stars_not_in_a_multiple and roots_of_trees are + # simply convenient partitions of particles_in_encounter. + # They are pointers into the underlying particle set. + + scale_top_level_list(stars_not_in_a_multiple, + roots_of_trees, + self.kepler, + final_scale, + self.gravity_constant, + self.global_debug) + + # 6b. Break up wide top-level binaries. Do this after + # rescaling because we want to preserve binary binding + # energies. Also place the wide binaries at pericenter to + # minimize the tidal error. + + # Number of top-level nodes. + lt = len(stars_not_in_a_multiple) + len(roots_of_trees) + + # Roots to be deleted after the loop. + roots_to_remove = [] + + for root in roots_of_trees: + comp1 = root.child1 + comp2 = root.child2 + + mass,semi,ecc,r,E,t = \ + get_component_binary_elements(comp1, + comp2, + self.kepler) + apo = semi*(1+ecc) + if self.retain_binary_apocenter: + binary_scale = apo + else: + binary_scale = 2*semi # (the more conservative choice) + + # Estimate the maximum perturbation on this binary due to + # its current strongest external perturber. + + max_perturbation = 0.0 + if len(sorted_perturbations) > 0: + max_perturbation = \ + 2*sorted_perturbations[0]*binary_scale**3/mass + perturber = sorted_stars[0] + perturber_distance = sorted_distances[0] + + # Check that other stars involved in the encounter but not + # in this multiple are not the dominant perturbation. + + stars_to_check = Particles() + for t in binaries.iter_binary_trees(): + if t.particle != root: # exclude self interaction + stars_to_check.add_particles(t.get_leafs_subset()) + + #while len(roots_to_check) > 0: + # r = roots_to_check.pop() + # if r != root: + # if hasattr(r, "child1"): + # if r not in roots_to_check: + # roots_to_check.append(r) + # else: + # stars_to_check.extend(r) + try: + stars_to_check.remove_particle(star1) + except KeysNotInStorageException: + #print 'failed to remove star1' + pass + try: + stars_to_check.remove_particle(star2) + except KeysNotInStorageException: + #print 'failed to remove star2' + pass + + # Check perturbation due to stars_to_check on root. + + for s in stars_to_check: + distance = (s.position - root.position).length() + pert = s.mass / distance**3 + s_perturbation = 2*pert*binary_scale**3/mass + if self.global_debug > 1: + print("star %s, distance %s, pert %s, s_pert %s, max_pert %s" \ + % (s.id, distance, pert, s_perturbation, + max_perturbation)) + if s_perturbation > max_perturbation: + max_perturbation = s_perturbation + perturber = s + perturber_distance = distance + + #if binary_scale > rad12: + if max_perturbation < self.wide_perturbation_limit \ + or self.repeat_count > 9: + if self.global_debug > 0: + print('accepting lightly perturbed or repeat binary', \ + name_pair(comp1,comp2)) + if self.global_debug > 1: + print(' semi =', semi, 'E/mu =', E) + print(' apo =', apo, 'peri =', semi*(1-ecc)) + if max_perturbation > 0: + if self.global_debug > 1: + print(' strongest perturber is', perturber.id, \ + 'with apo perturbation', max_perturbation) + print(' nearest neighbor is', perturber.id, \ + 'at distance', perturber_distance) + print(' repeat_count =', self.repeat_count) + else: + if max_perturbation > 0: + print(' perturbation = 0') + self.repeat_count = 0 # probably unnecessary + sys.stdout.flush() + + else: + if self.global_debug > 0: + if max_perturbation > 0: + print('splitting perturbed binary', \ + name_pair(comp1,comp2)) + if self.global_debug > 1: + print(' semi =', semi, 'E/mu =', E) + print(' apo =', apo, 'peri =', semi*(1-ecc)) + print(' strongest perturber is', perturber.id, \ + 'with apocenter perturbation', max_perturbation) + print(' nearest neighbor is', perturber.id, \ + 'at distance', perturber_distance) + sys.stdout.flush() + + # See the "special case" logic in + # scale_top_level_list(). If this is a sole bound + # top-level object, it has already been scaled to the + # desired separation and should *not* be modified + # here. Otherwise, move the components past + # periastron to initial_scatter_scale. + + if lt > 1: + + # Could use rescale_binary_components() for this, + # but code here is more compact, since we have + # already initialized the kepler structure. + + cmpos = root.position + cmvel = root.velocity + if self.global_debug > 1: + print('moving binary to periastron') + self.kepler.advance_to_periastron() + if self.global_debug > 1: + print('advancing binary to', final_scale) + sys.stdout.flush() + self.kepler.advance_to_radius(final_scale) + + dx = quantities.AdaptingVectorQuantity() + dx.extend(kep.get_separation_vector()) + dv = quantities.AdaptingVectorQuantity() + dv.extend(kep.get_velocity_vector()) + + f1 = comp1.mass/root.mass + comp1.position = cmpos - f1*dx + comp1.velocity = cmvel - f1*dv + comp2.position = cmpos + (1-f1)*dx + comp2.velocity = cmvel + (1-f1)*dv + + # Changing the list here would disrupt the loop + # bookkeeping. Remove any split-up roots after the + # loop, then recalculate all data structures and + # restart the loop. + + #particles_in_encounter.remove_particle(root) + roots_to_remove.append(root) + + # Note that removing the root will reinstate the + # children as top-level objects: + + if len(roots_to_remove) > 0: + + for r in roots_to_remove: + particles_in_encounter.remove_particle(r) + + # Recompute the tree structure. + + binaries = \ + trees.BinaryTreesOnAParticleSet(particles_in_encounter, + "child1", "child2") + # Single stars. + stars_not_in_a_multiple = binaries.particles_not_in_a_multiple() + + # Multiple centers of mass. + roots_of_trees = binaries.roots() + + #---------------------------------------------------------------- + # 7. Add the new top-level nodes to the gravity module. + + top_level_nodes = stars_not_in_a_multiple + roots_of_trees + + # Terminology from the PDF description: + + KE3 = top_level_nodes.kinetic_energy() + E3 = KE3 + top_level_nodes.potential_energy(G=self.gravity_constant) + + phi_ins = potential_energy_in_field(top_level_nodes, + stars - scattering_stars, + G=self.gravity_constant) + + Emul_final = zero + for tree in binaries.iter_binary_trees(): + isbin, dEmul = get_multiple_energy2(tree, self.gravity_constant) + Emul_final += dEmul + + dphi_2 = E2 - Emul_final - E3 + + if self.global_debug > 2: + print('E3 =', E3) + print('phi_ins =', phi_ins) + print('Emul_final =', Emul_final) + print('dphi_2 =', dphi_2) + + # 7a. Set radii to reflect multiple structure. + + set_radii(particles_in_encounter, self.kepler, self.global_debug) + + # Print diagnostics on added particles. Strip dimensions + # because of numpy problem noted below. + + if self.global_debug > 0: + print('final top-level:', end=' ') + r = zero + v = zero + vr = zero + for i in top_level_nodes: + if self.global_debug > 0: + print(i.id, '('+str(i.radius)+')', end=' ') + for j in top_level_nodes: + if i.id > j.id: + rij = ((i.position-j.position)**2).sum().sqrt() + if rij > r: + r = rij + v = ((i.velocity-j.velocity)**2).sum().sqrt() + vr = ((j.velocity-i.velocity) \ + * (j.position-i.position)).sum() + + if self.global_debug > 0: + print('') + print('M =', top_level_nodes.mass.sum(), end=' ') + print('Etop =', Etop) + if self.global_debug > 1 and len(top_level_nodes) > 1: + print(' r =', r) + print(' v =', v) + print(' v.r =', vr) + #print 'top_level_nodes:' + #print top_level_nodes + sys.stdout.flush() + + # Update the gravity module with the new data. + self.after.add_particles(stars_not_in_a_multiple) + + # 7b. Add stars not in a binary to the gravity code. + if len(stars_not_in_a_multiple) > 0: + #print 'adding stars_not_in_a_multiple:' + #print stars_not_in_a_multiple + gravity_stars.add_particles(stars_not_in_a_multiple) + + # 7c. Add the roots to the gravity code + multiples_particles = Particles() + multiples_particles.id = None + + for tree in binaries.iter_binary_trees(): + tree.particle.id = assign_id_to_root(tree) # assign CM ID (was 0) + #tree.particle.components = subset + #print 'adding particle:' + #print tree.particle + gravity_stars.add_particle(tree.particle) + self.after.add_particle(tree.particle) + multiples_particles.add_particle(tree.particle) + + if self.global_debug > 1: + print("multiples: interaction products: singles:", \ + stars_not_in_a_multiple.id, "multiples: ", \ + multiples_particles.id) + + # 7d. Store all trees in memory for later reference. + + # Note this is actually where the trees get added to the + # multiples module, and is the appropriate place to modify any + # of the leaves / roots in the module. Also this is what as + # getting deleted in a call to expand_encounter, but is + # unmodified in a call to stars - Josh. + + for tree in binaries.iter_binary_trees(): + self.root_to_tree[tree.particle] = tree.copy() + + # Return enough information to monitor all energy errors. + + dE_top = E3 - E0 + dphi_top = phi_ins - phi_rem + dEmul = Emul_final - Emul_init + dphi_int = dphi_2 - dphi_1 + + #------------------------------------------------------- + # Flag (but don't yet correct) large tidal corrections. + + dph = dphi_top/KE3 + if abs(dph) > 1.e-2: # 1.e-2 is small but otherwise arbitrary + if self.global_debug > 0: + print('*** tidal correction =', dph, 'KE ***') + #print 'initial configuration: phi =', \ + # potential_energy_in_field(scattering_stars, + # stars - scattering_stars, + # G=self.gravity_constant) + pminmin, fminmin, dxminmin \ + = find_nn2(scattering_stars, stars-scattering_stars, + self.gravity_constant) + if pminmin != None: + if self.global_debug > 1: + print('closest field/list pair is', \ + str(fminmin.id)+'/'+str(pminmin.id), \ + ' distance/scale =', dxminmin/initial_scale) + #print 'final configuration: phi =', \ + # potential_energy_in_field(top_level_nodes, + # stars - scattering_stars, + # G=self.gravity_constant) + pminmin, fminmin, dxminmin \ + = find_nn2(top_level_nodes, stars-scattering_stars, + self.gravity_constant) + if pminmin != None: + if self.global_debug > 1: + print('closest field/list pair is', \ + str(fminmin.id)+'/'+str(pminmin.id), \ + ' distance/scale =', dxminmin/initial_scale) + #------------------------------------------------------- + + # Experimental code to try to correct external tidal errors. + # Compare dphi_top with range of possible quadrupole + # corrections due to closest perturber. Start with the + # simplest case. + # + # tidal potential change is dphi_top + # multiple center of mass is cmpos + # perturbers are in largest_perturbers + + if self.check_tidal_perturbation \ + and len(particles_in_encounter) == 2 and len(top_level_nodes) == 2: + + print('checking quadrupole perturbations') + + # *** Retain unitless code for now (Steve, 4/18). *** + + m1 = top_level_nodes[0].mass + m2 = top_level_nodes[1].mass + dx = top_level_nodes[1].position - top_level_nodes[0].position + x = (dx**2).sum().sqrt() + print('x =', x, 'M =', m1+m2) + + for p in largest_perturbers: + m3 = p.mass + id = p.id + dr = p.position - cmpos + r = (dr**2).sum().sqrt() + phi = -self.gravity_constant*M*m3/r + dphiQ = -(self.gravity_constant*(m1*m2/M)*m3/r)*(x/r)**2 + print(' ', str(id)+':', 'r =', r, 'm =', p.mass, \ + 'dphi_top/dphiQ =', dphi_top/dphiQ) + + return False, dE_top, dphi_top, dEmul, dphi_int, dE_int, \ + particles_in_encounter + + def resolve_collision(self, + particles, + final_scatter_scale, + min_scatter_scale, + end_time, + delta_t): + + pre = 'encounter:' # identifier for all output + + # Take the system described by particles and evolve it forward + # in time until it is over. Don't update global quantities, + # don't interpret the outcome. Return the energy error due to + # the smallN integration. + + if self.debug_encounters: + delta_t *= 0.1 + + initial_delta_t = delta_t + if self.global_debug > 1: + print(pre, 'evolving to time', end_time) + print(pre, 'initial step =', initial_delta_t) + + # Allow delta_t to increase, with an upper limit. (The factor + # of 25 below should permit quasi-stable systems to be + # detected.) + + delta_t_max = 64*delta_t + while delta_t_max < end_time/25: delta_t_max *= 2 + + # Save some useful initial quantities. + + initial_position = particles.position + initial_velocity = particles.velocity + initial_cmvel = particles.center_of_mass_velocity() # should be 0 + initial_ke = particles.kinetic_energy() + initial_end_time = end_time + + # Allow the possibility of repeating the encounter if it fails + # to terminate. + + loop_count = 0 + loop_max = 10 + pert = 0.001 # retry option 1 + pert_fac = 10.**(1./loop_max) + scale_fac = (min_scatter_scale + / final_scatter_scale)**(2./loop_max) # option 2 + end_time_fac = 1.5 # option 2 + over = 0 + + while loop_count < loop_max: + + loop_count += 1 + #print pre, 'loop_count =', loop_count + #sys.stdout.flush() + + resolve_collision_code \ + = self.resolve_collision_code_creation_function() + + # Channel to copy values from the code to the set in memory. + + channel = resolve_collision_code.particles.new_channel_to(particles) + + time = 0 * end_time + + resolve_collision_code.set_time(time) + resolve_collision_code.particles.add_particles(particles) + resolve_collision_code.commit_particles() + + delta_t = initial_delta_t + resolve_collision_code.set_break_scale(final_scatter_scale) + + initial_scatter_energy \ + = self.get_total_energy(resolve_collision_code) + + if self.global_debug > 1: + print(pre, 'number_of_stars =', len(particles), ' ', \ + particles.id) + print(pre, 'initial energy =', initial_scatter_energy) + #print particles + + if self.debug_encounters: + print(pre, '### START ENCOUNTER ###') + print(pre, '### snapshot at time %f' % 0.0) + for p in particles: + print(pre, '### id=%d, x=%f, y=%f, z=%f,'\ + 'vx=%f, vy=%f, vz=%f' % \ + (p.id, p.x.number, p.y.number, p.z.number, + p.vx.number, p.vy.number, p.vz.number)) + sys.stdout.flush() + + #------------------------------------------------------------ + # + # If the encounter fails to terminate within the specified + # time we have some options: + # + # 1. Try perturbing the encounter in various energy + # conservative ways, starting from the original + # velocities. + # + # 2. Modify the termination conditions. This is + # potentially less expensive, but may not lead to a clean + # outcome. Increasing end_time simply involves extending + # the while loop; changing final_scatter_scale requires a + # new calculation. + + option = 2 + inner_loop = 1 + + ############################################################# + # Set this to enable step-by-step debugging output. + # resolve_collision_code.parameters.outfile='abc.dat' + # + # e.g. + # if self.gravity_code.model_time.number > 31.4159: + # resolve_collision_code.parameters.outfile = 'debug.dat' + ############################################################# + + while time < end_time: + + tt = time + time += delta_t + # print pre, '...to time', time + # sys.stdout.flush() + + # Work with internal substeps of initial_delta_t to + # allow checks for quasi-stable motion. + + while tt < time: + + tt += initial_delta_t + if tt > time: tt = time + + if 0: + print(pre, ' ...', time, tt, \ + 'model_time =', \ + resolve_collision_code.model_time) + sys.stdout.flush() + + resolve_collision_code.evolve_model(tt) + + if 0: + print(pre, ' ...back:', \ + ': model_time =', \ + resolve_collision_code.model_time) + sys.stdout.flush() + + tt = resolve_collision_code.model_time + + # DEBUGGING: + if self.debug_encounters: + print(pre, '### snapshot at time %f' \ + % time.number) + #resolve_collision_code.update_particle_tree() + #resolve_collision_code.update_particle_set() + resolve_collision_code.particles \ + .synchronize_to(particles) + channel.copy() + for p in particles: + print(pre, '### id=%d, x=%f, y=%f, z=%f,'\ + 'vx=%f, vy=%f, vz=%f' % \ + (p.id, p.x.number, p.y.number, p.z.number, + p.vx.number, p.vy.number, p.vz.number)) + sys.stdout.flush() + + # The argument final_scatter_scale is used to + # limit the size of the system. It has to be + # supplied again because the code that determines + # if the scattering is over isn't necessarily the + # same as resolve_collision_code. However, + # currently only smallN has an "is_over()" + # function. + # + # Return values: 0 - not over + # 1 - over + # 2 - quasi-stable system + # 3 - size exceeded limit + # + # Note that this is really a stopping condition, + # and should eventually be handled that way. TODO + # + # If over = 3, if the parameters were properly + # chosen, the resulting system should stil be + # usable. The interface function will take steps + # to return proper hierarchical structure even if + # the inner subsystem is not well resolved. + # + # Note that we are currently ignoring any + # possibility of a physical collision during the + # multiples encounter. TODO + + over = resolve_collision_code.is_over\ + (final_scatter_scale, + 0) # verbose = 0 + + if over: + final_scatter_energy \ + = self.get_total_energy(resolve_collision_code) + scatter_energy_error \ + = final_scatter_energy - initial_scatter_energy + + if self.global_debug > 1: + print(pre, 'over =', over, 'at time', tt) + #print pre, 'initial energy =', \ + # initial_scatter_energy + #print pre, 'final energy =', \ + # final_scatter_energy + #print pre, 'energy error =', \ + # scatter_energy_error + print(pre, 'fractional energy error =', \ + scatter_energy_error/initial_scatter_energy) + if self.debug_encounters: + print(pre, '### END ENCOUNTER ###') + sys.stdout.flush() + + # Create a tree in the module representing the + # binary structure. + + resolve_collision_code.update_particle_tree(over) + + # Note: A quasi-stable system (over = 2) + # should be handled properly, as it will + # appear to be a bound top-level binary. If + # over = 3, the top level should be a receding + # bound or unbound system, and the tree + # structure should still be usable. + + # Note that center of mass particles are now + # part of the particle set. + + # Return the tree structure to AMUSE. + # Children are identified by + # get_children_of_particle in interface.??, + # and the information is returned in the copy + # operation. + + resolve_collision_code.update_particle_set() + resolve_collision_code.particles \ + .synchronize_to(particles) + #print "resolve_collision_code.particles.radius"\ + # , resolve_collision_code.particles.radius + channel.copy() + #resolve_collision_code.stop() + + if 1: + + # Count number of top-level multiples. Must + # be >0 for the post-encounter bookkeeping to + # work. + + binaries = trees.BinaryTreesOnAParticleSet( + particles, "child1", "child2") + singles = binaries.particles_not_in_a_multiple() + multiples = binaries.roots() + if self.global_debug > 0: + print('after', pre, len(singles), \ + 'single(s),', \ + len(multiples), 'multiple(s)') + + return scatter_energy_error + + if tt >= 0.99999999*time: break # avoid roundoff + + # -- end of while tt < time: loop -- + + time = resolve_collision_code.model_time + if not self.debug_encounters: + if delta_t < delta_t_max \ + and time > 0.999999*4*delta_t: + delta_t *= 2 + if self.global_debug > 1: + print(pre, 'setting delta_t =', delta_t) + sys.stdout.flush() + + if time > 0.99999999*end_time: # avoid roundoff + + # Encounter has failed to terminate and we are + # about to break out of the loop. If option = 2 + # and this is an odd-numbered loop, just increase + # end_time (once only). Otherwise, break and + # allow other options to take effect. + + if option == 2 and 2*(loop_count/2) != loop_count \ + and inner_loop == 1: + + # Adjust the bulk scattering parameters. + # Simply increase end_time. + + end_time *= end_time_fac + + # Same print output as below. + + if self.global_debug > 1: + print(pre, 'loop', loop_count, ' over =', over) + print('increasing end_time to', end_time) + print('-----') + + inner_loop = 2 + loop_count += 1 + + else: + break + + # -- end of while time < end_time: loop -- + + # As currently coded, if we get here we have not + # overwritten the original particle set, particles. + # Nevertheless, we restore particle data here prior + # to a retry. + + particles.position = initial_position + particles.velocity = initial_velocity + + if self.global_debug > 1: + print(pre, 'loop', loop_count, ' over =', over) + + if option == 1: + + # Perturbing the encounter can be done in several + # ways, of increasing intrusiveness and decreasing + # reasonableness. + # + # 1. Randomize the phases of all binary orbits. + # 2. Randomize the orientations of all binary orbits. + # 3. Perturb (jiggle) the top-level orbits. + # 4. Jiggle all velocities. + # + # In all cases, the total energy must be preserved and + # the CM motion must remain at the origin. However, + # in case 4, the total multiple energy and hence the + # bookkeeping will be compromised unless we explicitly + # correct it -- need an additional return value. + # + # TODO: We should implement options 1-3 -- these + # require scattering_stars to be passed as an + # argument. + # + # For now, choose the least desirable but easiest + # option #4, with increasing pert as loop_count + # increases. + + ran = 1 + pert*(2*numpy.random.random(len(particles)) - 1) + for k in range(len(particles)): + particles[k].velocity *= ran[k] + + # Preserve momentum and energy. + + final_cmvel = particles.center_of_mass_velocity() + particles.velocity -= final_cmvel - initial_cmvel + final_ke = particles.kinetic_energy() + particles.velocity *= math.sqrt(initial_ke/final_ke) + + pert *= pert_fac + print('retrying with pert =', pert) + + elif option == 2: + + # Adjust the bulk scattering parameters. First + # increase end_time, then reduce and increase + # final_scatter_scale, etc. Should only be able to + # get here if loop_count is even. End_time has + # already been increased. Use the larger version, but + # decrease final_scatter_scale. + + final_scatter_scale *= scale_fac + + print('retrying with final_scatter_scale =', final_scatter_scale) + print(' end_time =', end_time) + + print('-----') + + raise DidNotFinishException( + pre + \ + " Small-N simulation did not finish before end time {0}". + format(end_time) + ) + +def openup_tree(star, tree, particles_in_encounter): + + # List the leaves. + + leaves = tree.get_leafs_subset() + #print 'leaves:' + #print leaves + + original_star = tree.particle + + # Compare with the position stored when replacing the particles + # with the root particle, and move the particles accordingly. + # Note that once the CM is in the gravity module, the components + # are frozen and the coordinates are absolute, so we need the + # original coordinates to offset them later. + + # Maybe better just to store relative coordinates? TODO + + dx = star.x - original_star.x + dy = star.y - original_star.y + dz = star.z - original_star.z + dvx = star.vx - original_star.vx + dvy = star.vy - original_star.vy + dvz = star.vz - original_star.vz + + leaves.x += dx + leaves.y += dy + leaves.z += dz + leaves.vx += dvx + leaves.vy += dvy + leaves.vz += dvz + + particles_in_encounter.add_particles(leaves) + +def phi_tidal(star1, star2, star3, G): # compute tidal potential of + # (star1,star2) relative to star3 + phi13 = -G*star1.mass*star3.mass/(star1.position-star3.position).length() + phi23 = -G*star2.mass*star3.mass/(star2.position-star3.position).length() + m12 = star1.mass + star2.mass + cm = Particles([star1, star2]).center_of_mass() + phicm = -G*m12*star3.mass/(star3.position-cm.position).length + return phi13+phi23-phicm + +def find_nn(plist, field, G): + + # Find and print info on the closest field particle (as + # measured by potential) to any particle in plist. + + pminmin = None + fminmin = None + phiminmin = zero + for f in field: + dx = (plist.position - f.position).lengths() + phi = -G*f.mass*plist.mass/dx + phimin = zero + dxmin = 1.e30 + pmin = None + for i in range(len(phi)): + if phi[i] < phimin: + phimin = phi[i] + dxmin = dx[i] + pmin = plist[i] + if phimin < phiminmin: + phiminmin = phimin + pminmin = pmin + dxminmin = dxmin + fminmin = f + + return pminmin, fminmin, dxminmin + +def find_nn2(plist, field, G): + + # Find and print info on the closest field particle (as + # measured by potential) to any particle in plist. + # revised, faster version of find_nn + + pminmin = None + fminmin = None + phiminmin = zero + + for p in plist: + + dx = (p.position - field.position).lengths() + phi = -G*field.mass*p.mass/dx + #phi = numpy.divide(numpy.prod([-1,G,field.mass,p.mass]),dx) + phimin = zero + dxmin = 1.e30 + pmin = None + j = numpy.argmin(phi.number) + phimin = phi[j] + dxmin = dx[j] + pmin = p + if phimin < phiminmin: + phiminmin = phimin + pminmin = pmin + dxminmin = dxmin + fminmin = field[j] + + return pminmin, fminmin, dxminmin + +def find_binaries(particles, G): + + # Search for and print out bound pairs using a numpy-accelerated + # N^2 search. + + for p in particles: + mu = p.mass*particles.mass/(p.mass+particles.mass) + dr = (particles.position - p.position).lengths() + dv = (particles.velocity - p.velocity).lengths() + E = 0.5*mu*dv*dv - G*p.mass*particles.mass/dr + indices = numpy.argsort(E.number) + sorted_E = E[indices] + Emin = sorted_E[1].number + if Emin < -1.e-4 and p.id < particles[indices[1]].id: + print('bound', p.id, particles[indices[1]].id, Emin) + +def potential_energy_in_field(particles, field_particles, + smoothing_length_squared = zero, + G=constants.G): + """ + Returns the total potential energy of the particles in the particles + set. argument field_particles: the external field consists of these + (i.e. potential energy is calculated relative to the field + particles) argument smoothing_length_squared: the smoothing length + is added to every distance. argument G: gravitational constant, + need to be changed for particles in different units systems + """ + + if len(field_particles) == 0: + return zero + + sum_of_energies = zero + for particle in particles: + dr_squared = (particle.position-field_particles.position).lengths_squared() + dr = (dr_squared+smoothing_length_squared).sqrt() + m_m = particle.mass * field_particles.mass + potentials = -m_m/dr + energy_of_this_particle = potentials.sum() + sum_of_energies += energy_of_this_particle + imin = numpy.argmin(potentials.number) + imin = numpy.argmin(dr.number) + + return G * sum_of_energies + +def offset_particle_tree(particle, dpos, dvel): + + # Recursively offset a particle and all of its descendants by + # the specified position and velocity. + + if not particle.child1 is None: + offset_particle_tree(particle.child1, dpos, dvel) + if not particle.child2 is None: + offset_particle_tree(particle.child2, dpos, dvel) + particle.position += dpos + particle.velocity += dvel + # print 'offset', int(particle.id), 'by', dpos; sys.stdout.flush() + +def rescale_binary_components(comp1, comp2, kep, scale, compress=True): + + # Rescale the two-body system consisting of comp1 and comp2 to lie + # inside (compress=True) or outside (compress=False) distance + # scale of one another. If compress=True, the final orbit will be + # receding; otherwise it will be approaching. In a typical case, + # scale is comparable to the separation at which the interaction + # started. It is possible that the input system is very close to + # periastron. To avoid problems with very eccentric systems, + # force the system to be scaled to a separation of at least + # 0.1*scale (0.1 is ~arbitrary: should be <1, and not too small). + + if compress: + pre = 'rescale_binary_components(-):' + else: + pre = 'rescale_binary_components(+):' + + pos1 = comp1.position + pos2 = comp2.position + sep12 = ((pos2-pos1)**2).sum() + 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 + + mass = comp1.mass + comp2.mass + rel_pos = pos2 - pos1 + rel_vel = vel2 - vel1 + + if 0: + print(pre, 'mass =', mass) + print(pre, 'pos =', rel_pos) + print(pre, 'vel =', rel_vel) + + 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 0: + print(pre, 'M, th, a, e, =', M, th, a, e) + print(pre, 'compress =', compress) + print(pre, sep12, scale**2, min_scale**2) + + rescale = (compress and sep12 > scale**2) \ + or (not compress and sep12 < scale**2) + + min_scale = 0.1*scale # see note above + + if compress == True: + rescale = rescale or sep12 < min_scale**2 + + if rescale: + + #print 'rescaling components', int(comp1.id), \ + # 'and', int(comp2.id), 'to separation', scale + # sys.stdout.flush() + + # print pre, 'a, e =', a, e + if e < 1: + peri = a*(1-e) + apo = a*(1+e) + else: + peri = a*(e-1) + apo = peri+a # OK - used only to reset scale + + if compress: + + # Logic here is to handle special configurations. + + limit = peri + 1.e-4*(apo-peri) # numbers are + if limit > 1.1*peri: limit = 1.1*peri # ~arbitrary + if limit < min_scale: limit = min_scale + if scale < limit: + # print pre, 'changed scale from', scale, 'to', limit + scale = limit + + if M < 0: + # print pre, 'advance_to_periastron' + kep.advance_to_periastron() + # print pre, 'advance_to_radius', scale + kep.advance_to_radius(scale) + else: + if kep.get_separation() < scale: + # print pre, 'advance_to_radius', scale + kep.advance_to_radius(scale) + else: + # print pre, 'return_to_radius', scale + kep.return_to_radius(scale) + + # Note: Always end up on an outgoing orbit. If periastron + # > scale, we are now just past periastron. + + else: + limit = apo - 0.01*(apo-peri) + # print pre, "limit:", limit, apo, peri, scale , M, e + if scale > limit: + # print pre, 'changed scale from', scale, 'to', limit + scale = limit + + #print "INPUT:", kep.get_separation_vector() + #print "true_anomaly:", M, kep.get_separation() , scale + if M > 0: + kep.return_to_periastron() + kep.return_to_radius(scale) + else: + if kep.get_separation() < scale: + kep.return_to_radius(scale) + else: + kep.advance_to_radius(scale) + + # Note: Always end up on an incoming orbit. If + # apastron < scale, we are now just before apastron. + + #print 'scale =', scale, 'sep =', kep.get_separation(), 'M =', M + + new_rel_pos = kep.get_separation_vector() + new_rel_vel = kep.get_velocity_vector() + + # 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, + # Steve doesn't know how to copy a numpy array with units... + # TODO - help? + #print "REL POS:", new_rel_pos + 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. + #print "DP1:", newpos1-pos1, hasattr(comp1, 'child1') + #print "DP2:", newpos2-pos2, hasattr(comp2, 'child1') + if hasattr(comp1, 'child1'): + offset_particle_tree(comp1, newpos1-pos1, newvel1-vel1) + if hasattr(comp2, 'child1'): + offset_particle_tree(comp2, newpos2-pos2, newvel2-vel2) + + # print pre, 'done' + sys.stdout.flush() + + return a + +def offset_children(n, dx, dv): + if n.child1 != None: + n.child1.position -= dx + n.child1.velocity -= dv + offset_children(n.child1, dx, dv) + if n.child2 != None: + n.child2.position -= dx + n.child2.velocity -= dv + offset_children(n.child2, dx, dv) + +def compress_nodes(node_list, scale, G): + + local_debug = False + + # Compress (or expand) the top-level nodes in node_list to lie + # within diameter scale. Rescale velocities to conserve total + # energy (but currently not angular momentum -- TODO). + + pre = 'compress_nodes:' + + # Compute the center of mass position and velocity of the + # top-level system. + + cmpos = node_list.center_of_mass() + cmvel = node_list.center_of_mass_velocity() + + # Child positions and velocities will not be explicitly changed by + # the scaling. Temporarily store child data as offsets relative + # to the root. We will undo this at the end, immediately before + # returning. + + for n in node_list: + if n.child1 != None: + dx = n.position + dv = n.velocity + offset_children(n, dx, dv) + + if local_debug: + print('node_list:') + print(node_list) + print('top_level:') + print_top_level(node_list, G) + + x0 = (node_list[0].position**2).sum().sqrt() + lunit = x0/x0.number + v0 = (node_list[0].velocity**2).sum().sqrt() + vunit = v0/v0.number + vunit2 = vunit**2 + + # Compute various measures of the size, potential, and kinetic + # energy of the system in the center of mass frame. + + size = zero # max distance(**2) from center of mass + + rijmin = 1.e100*lunit # minimum separation + imin = -1 + jmin = -1 + phimin = zero # minimum potential + ipmin = -1 + jpmin = -1 + + n = len(node_list) + pot = zero + kin = zero + dr = numpy.zeros((n,n)) # unit = lunit + dv2 = numpy.zeros((n,n)) # unit = vunit2 + for i in range(n): + m = node_list[i].mass + posi = node_list[i].position + pos = posi - cmpos + veli = node_list[i].velocity + vel = veli - cmvel + r2 = (pos**2).sum() + if r2 > size: + size = r2 + kin += m*(vel**2).sum() + dpot = zero + for j in range(i+1,n): + mj = node_list[j].mass + dposj = node_list[j].position - posi + rij = (dposj**2).sum().sqrt() + dphij = -G*mj/rij + dpot += dphij + phij = m*dphij + if rij < rijmin: + rijmin = rij + imin = i + jmin = j + if phij < phimin: + phimin = phij + ipmin = i + jpmin = j + dvelj = node_list[j].velocity - veli + dr[i,j] = rij/lunit + dv2[i,j] = (dvelj**2).sum()/vunit2 + if dpot != zero: + pot += m*dpot + size = size.sqrt() + kin /= 2 + rphmin = -(node_list[ipmin].mass*node_list[jpmin].mass)/phimin + + if local_debug: + print(pre, 'scale =', scale) + print(pre, 'size =', size) + print(pre, 'rijmin =', rijmin, node_list[imin].id, node_list[jmin].id) + print(pre, 'rphmin =', rphmin, node_list[ipmin].id, node_list[jpmin].id) + + fac = 0.5*scale/size # scale to radius + #fac = scale/rijmin # scale to minimum distance + #fac = scale/rphmin # scale to minimum potential distance + + if local_debug: + print(pre, 'fac =', fac) + + # Compress (or expand) the system and increase (or decrease) the + # velocities (relative to the center of mass) to preserve the + # energy. If fac > 1, expansion is always OK if E > 0, which it + # should be at this point (but check anyway...). May have E < 0 + # if we have a system with small negative energy, stopped because + # it is too big. + + # An additional consideration (Steve, 1/2017) is that all + # top-level nodes are mutually unbound at the end of the + # scattering, by construction, but this may not be preserved by + # a simple uniform rescaling of the system. In that case, an + # unphysical extra interaction may follow the scattering we + # thought was "over." Currently we check for this possibility, + # then modify the way in which velocities are scaled to + # compensate. NOT guaranteed to work in all cases, and the code + # is ugly... + + vfac2 = 1-(1/fac-1)*pot/kin + #print "vfac2 =", vfac2 + + if vfac2 < 0: + print(pre, "Can't expand top level system to rjmin > ri+rj") + print("fac =", fac, " pot =", pot, " kin =", kin) + sys.stdout.flush() + f = pot/(kin+pot) + vfac2 = 0.0 # ??? + + vfac = math.sqrt(vfac2) + if local_debug: + print("vfac =", vfac) + print(pre, 'dr:') + print(dr) + print(pre, 'dv2:') + print(dv2) + + bound_pairs = [] + unbound = numpy.ones(n) + for i in range(n): + mi = node_list[i].mass + bound = False + for j in range(i+1,n): + mj = node_list[j].mass + mu = mi*mj/(mi+mj) + Eijold = 0.5*mu*dv2[i,j]*vunit2 - G*mi*mj/(dr[i,j]*lunit) + Eijnew = 0.5*mu*vfac2*dv2[i,j]*vunit2 - G*mi*mj/(fac*dr[i,j]*lunit) + if Eijnew.number <= 0.0: + #print 'bound', i, j, Eijold, Eijnew + bound = True + bound_pairs.append((i,j)) + unbound[i] = 0 + unbound[j] = 0 + + print(pre, 'bound pairs:', bound_pairs) + unbound_nodes = [] + for i in range(n): + if unbound[i] == 1: + unbound_nodes.append(i) + print(pre, 'unbound_nodes:', unbound_nodes) + + if len(unbound_nodes) == 0: + + # Live with unphysical bound pairs for now. TODO + + print('*** warning: no unbound nodes ***') + bound_pairs = [] + + if len(bound_pairs) > 0: + + # Strategy #1: Scale positions uniformly as planned, but + # adjust the velocity scaling for pairs whose binding energy + # would become negative. Strategy #2 (unimplemented) would be + # to modify the spatial scaling by scaling bound pairs only to + # scale, then adjust velocities as in #1. Strategy #3 (also + # unimplemented) would be to not scale bound pairs at all. + + energy = pot + kin # initial energy - conserved + + for n in node_list: + n.position = cmpos + fac*(n.position-cmpos) + dr *= fac + pot /= fac + + if local_debug: + print('kinetic energies:') + for n in node_list: + print(' ', n.id, 0.5*n.mass*((n.velocity-cmvel)**2).sum()) + + # First give the bound components enough relative velocity to + # just unbind them, keeping their center of mass velocity + # fixed. Note that, since Eij was > 0 and this prescription + # leaves Eij close to 0, this transformation should liberate + # energy for distribution to the rest of the system. + + kin2 = zero + kinCM = zero + for p in bound_pairs: + i = p[0] + j = p[1] + ni = node_list[i] + nj = node_list[j] + mi = ni.mass + mj = nj.mass + newvfac2 = 2.000001*(G*(mi+mj)/(dr[i,j]*lunit))/(dv2[i,j]*vunit2) + newvfac = math.sqrt(newvfac2) + massinv = 1./(mi+mj) + cmv = (mi*ni.velocity + mj*nj.velocity)*massinv + ni.velocity = cmv + newvfac*(ni.velocity-cmv) + nj.velocity = cmv + newvfac*(nj.velocity-cmv) + kin2 += 0.5*mi*mj*massinv*((ni.velocity-nj.velocity)**2).sum() + kinCM += 0.5*(mi+mj)*((cmv-cmvel)**2).sum() + + if local_debug: + print('KECM =', kin2+kinCM) + for i in unbound_nodes: + ni = node_list[i] + mi = ni.mass + kei = 0.5*mi*((ni.velocity-cmvel)**2).sum() + if local_debug: + print('KE', ni.id, kei) + kinCM += kei + + if local_debug: + print('energy =', energy, 'pot+kin2+kinCM =', pot+kin2+kinCM) + kin_to_distribute = energy - (pot+kin2+kinCM) + + if kin_to_distribute.number < 0: + print('*** warning: not enough kinetic energy ***') # TODO + + vfac2 = 1+kin_to_distribute/kinCM + vfac = math.sqrt(vfac2) + #print 'vfac =', vfac + + # Then apply an overall scaling to unbound nodes and bound CMs + # to conserve total energy. + + for i in unbound_nodes: + ni = node_list[i] + ni.velocity = cmvel + vfac*(ni.velocity-cmvel) + for p in bound_pairs: + i = p[0] + j = p[1] + ni = node_list[i] + nj = node_list[j] + mi = ni.mass + mj = nj.mass + massinv = 1./(mi+mj) + cmv = (mi*ni.velocity + mj*nj.velocity)*massinv + newcmv = cmvel + vfac*(cmv-cmvel) + ni.velocity += newcmv - cmv + nj.velocity += newcmv - cmv + + if len(bound_pairs) == 0: + + # Perform global scaling of position and velocity. + + for n in node_list: + n.position = cmpos + fac*(n.position-cmpos) + n.velocity = cmvel + vfac*(n.velocity-cmvel) + + # Child data have not yet been modified. Do so here. Note that + # child positions and velocities were temporarily offset to the + # top-level center of mass. + + for n in node_list: + if n.child1 != None: + dx = -n.position + dv = -n.velocity + offset_children(n, dx, dv) + + #print_top_level(node_list, G) + +def get_multiple_energy(node, kep): + + # Return the binary status and the total energy Etot of the + # specified tree node. The value of Etot is the total pairwise + # energy of all binary objects in the hierarchy. It does not + # include the tidal potential of one component on another (e.g. + # in a hierarchical triple Etot will be the sum of two binary + # energies only). + + # Note that kep should have been initialized with the correct + # converter to return the proper energy units. + + is_bin = 1 + Etot = zero + for level, x in node.iter_levels(): + particle = x + if not particle.child1 is None: + if level > 0: is_bin = 0 + child1 = particle.child1 + child2 = particle.child2 + M,a,e,r,Emu,t = get_component_binary_elements(child1, child2, kep) + mu = child1.mass*child2.mass/M + E = Emu*mu + Etot += E + return is_bin, Etot + +def get_multiple_energy2(node, G): + + # Return the binary status and the total energy of the specified + # tree node. Uses a value of G supplied by the caller. The + # returned value is the total energy of all leaves in the + # hierarchy, properly including tidal potentials, but excluding + # the center of mass energy. + + is_bin = 1 + Ecm = zero + + for level, x in node.iter_levels(): + if level == 0: + particle = x + M_comp = 0*particle.mass + vcm_comp = M_comp*particle.velocity + + if particle.id == 1000074: + pp = True + + break + + # List the leaves and do some additional work. Note that + # node.get_leafs_subset() seems to do the same thing... + + leaves_in_node = Particles(0) + + for level, x in node.iter_levels(): + particle = x + if level == 0: + + # Want to compute the top-level kinetic energy. Might + # expect + + vcm = particle.velocity + Ecm = 0.5*particle.mass*(vcm**2).sum() + + # but in some circumstances (e.g. a binary created in a + # many-body process), the position and velocity of the + # parent may not correctly reflect the center of mass of + # its children. + + if not particle.child1 is None: + if level > 0: + is_bin = 0 # not a multiple + else: + leaves_in_node.add_particle(particle) # list leaves + M_comp += particle.mass + vcm_comp += particle.mass*particle.velocity + + vcm_comp /= M_comp + Ecm_comp = 0.5*M_comp*(vcm_comp**2).sum() + + return is_bin, leaves_in_node.kinetic_energy() \ + + leaves_in_node.potential_energy(G=G) \ + - Ecm_comp + +def add_leaves(node, leaf_list): + if node.child1 == None: + leaf_list.add_particle(node) + else: + add_leaves(node.child1, leaf_list) + add_leaves(node.child2, leaf_list) + +def get_energy_of_leaves(particles, G): + leaves = Particles(0) + for p in particles: + if not hasattr(p, 'child1') or p.child1 == None: + leaves.add_particle(p) + #print 'get_energy_of_leaves():' + #print leaves + ke = leaves.kinetic_energy() + pe = leaves.potential_energy(G=G) + e = ke+ pe + #print ke, pe, e + return e + +def print_energies(stars, G): + + # Brute force N^2 over top level, pure python... + + top_level = stars.select(is_not_a_child, ["is_a_child"]) + + mass = zero + kinetic = zero + potential = zero + for t in top_level: + m = t.mass + x = t.x + y = t.y + z = t.z + vx = t.vx + vy = t.vy + vz = t.vz + mass += m + kinetic += 0.5*m*(vx**2+vy**2+vz**2) + dpot = zero + for tt in top_level: + if tt != t: + mm = tt.mass + xx = tt.x-x + yy = tt.y-y + zz = tt.z-z + dpot -= G*mm/(xx**2+yy**2+zz**2).sqrt() + potential += 0.5*m*dpot + + print('len(stars) =', len(stars)) + print('len(top_level) =', len(top_level)) + print('mass =', mass) + print('kinetic =', kinetic) + print('potential =', potential) + print('energy =', kinetic+potential) + sys.stdout.flush() + +def scale_top_level_list(singles, multiples, kep, scale, + gravity_constant, global_debug): + + pre = 'scale_top_level_list:' + + # The multiples code followed the particles until their + # interaction could be unambiguously classified as over. They may + # now be very far apart. Input singles and multiples are lists + # describing the final top-level structure of the interacting + # particles in the multiples code. Singles is a list of single + # stars. Multiples is a list of multiple centers of mass (with + # pointers to the internal structure). + + # Scale the positions and velocities of the top-level nodes to + # bring them within a sphere of diameter scale, conserving energy + # and angular momentum (if possible). Also offset all children to + # reflect changes at the top level -- TODO: will change if/when + # offsets are implemented... + + # Logic: 1 node - must be a binary, use kepler to reduce to scale + # 2 nodes - use kepler, reduce binary children too? TODO + # 3+ nodes - shrink radii and rescale velocities to preserve + # energy, but this doesn't preserve angular + # momentum TODO - also reduce children? TODO + + top_level_nodes = singles + multiples + + # Figure out the tree structure. + + ls = len(singles) + lm = len(multiples) + lt = ls + lm + + if global_debug > 1: + print(pre, 'ls =', ls, ' lm =', lm, ' lt =', lt) + sys.stdout.flush() + + if lt == 1: + if lm == 1: + + # Special case. We have a single bound binary node. Its + # children are the components we want to transform. Note + # that, if the components are binaries (or multiples), + # they must be stable, so it is always OK to move the + # components to periastron. + + # Note: Wide binaries will be split and returned to the + # large-scale dynamics module after return from this + # function. + + root = multiples[0] + + if global_debug > 1: + print(pre, 'bound binary node', scale) + #print '\nunscaled binary node:' + #print_multiple_recursive(root) + comp1 = root.child1 + comp2 = root.child2 + if global_debug > 1: + print(pre, 'scale:', scale) + semi = rescale_binary_components(comp1, comp2, kep, scale) + #true, mean = kep.get_angles() + #print 'true =', true, 'mean =', mean + #print 'scaled binary node:' + #print_multiple_recursive(root, kep) + + elif lt == 2: + + # We have two unbound top-level nodes, and we will scale them + # using kepler to the desired separation, hence conserving + # both energy and angular momentum of the top-level motion. + + # We might also want to scale the daughter nodes. Note as + # above that, if the daughters are binaries (or multiples), + # they must be stable, so it is always OK to move them to + # periastron. + + comp1 = top_level_nodes[0] + comp2 = top_level_nodes[1] + + if global_debug > 1: + print(pre, 'top-level unbound pair') + #print pre, '\nunscaled top-level pair:' + #print_pair_of_stars('pair', comp1, comp2, kep) + sys.stdout.flush() + semi = rescale_binary_components(comp1, comp2, kep, scale) + #print pre, '\nscaled top-level pair:' + #print_pair_of_stars('pair', comp1, comp2, kep) + #sys.stdout.flush() + + else: + + # We have three or more unbound top-level nodes. We don't + # know how to compress them in a conservative way. For now, + # we will conserve energy and think later about how to + # preserve angular momentum. TODO + + print(pre, lt, 'top-level nodes, scale =', scale) + #print lt, 'unscaled top-level nodes' + #print top_level_nodes + compress_nodes(top_level_nodes, scale, gravity_constant) + #print lt, 'scaled top-level nodes' + #print top_level_nodes + + # print pre, 'done' + sys.stdout.flush() + + # Don't attempt to correct or even return the tidal energy error. + # Manage all of this in the calling function, as desired. + + return + +def set_radius_recursive(node, kep, global_debug): + + if node.is_leaf(): return # nothing to be done + + # Propagate child radii upward. Since dynamical radius scales + # with mass, the radius of a parent is just the sum of the radii + # of the children. If we are simply handling 2-body encounters, + # that's all we need. The semi-major axis of a hard binary is + # less than the dynamical radius, by definition. However, we must + # include the size of a soft binary or multiple, which may be + # significantly larger than the dynamical radius of the center of + # mass. + + # Note that kep should have been initialized with the correct + # converter to return the proper energy units. + + rsum = zero + for child in node.iter_children(): + set_radius_recursive(child, kep, global_debug) + rsum += child.particle.radius + + # Currently rsum is the dynamical radius of the node. Check how it + # compares to the node's semimajor axis. + + M,semi,e,x,x,x = get_component_binary_elements(node.particle.child1, + node.particle.child2, kep) + + if rsum < 2*semi: + + # *** Factor of 2 here is ~arbitrary; should probably be set + # *** in the class definition. + + if global_debug > 0: + print('increasing radius for', node.particle.id, 'from', \ + rsum, 'to', 2*semi) + rsum = 2*semi + + node.particle.radius = rsum + +# Note: iter_children() lists the leaves lying under a given node of +# type BinaryTreeOnParticle. The child leaves are objects of type +# ChildTreeOnParticle. The particle associated with child x is +# x.particle. + +def set_radii(top_level_nodes, kep, global_debug): + for n in top_level_nodes.as_binary_tree().iter_children(): + set_radius_recursive(n, kep, global_debug) + +#---------------------------------------------------------------------- + +def print_elements(s, a, e, r, Emu, E): + + # Print binary elements in standard form. + + print('{0} elements a = {1} e = {2} r = {3} E/mu = {4} E = {5}'\ + .format(s, a, e, r, Emu, E)) + sys.stdout.flush() + +def print_pair_of_stars(s, star1, star2, kep): + m1 = star1.mass + m2 = star2.mass + M,a,e,r,E,t = get_component_binary_elements(star1, star2, kep) + print_elements(s, a, e, r, E, E*m1*m2/(m1+m2)) + print_multiple_recursive(star1, kep) + print_multiple_recursive(star2, kep) + +def print_multiple_recursive(m, kep, level=0): ##### not working? ##### + + # Recursively print the structure of (multiple) node m. + + print(' '*level, 'key =', m.key, ' id =', int(m.id)) + print(' '*level, ' mass =', m.mass) + print(' '*level, ' pos =', m.position) + print(' '*level, ' vel =', m.velocity) + sys.stdout.flush() + if not m.child1 is None and not m.child2 is None: + M,a,e,r,E,t = get_component_binary_elements(m.child1, m.child2, kep) + print_elements(' '+' '*level+'binary', a, e, r, E, + (E*m.child1.mass*m.child2.mass/M)) + if not m.child1 is None: + print_multiple_recursive(m.child1, kep, level+1) + if not m.child2 is None: + print_multiple_recursive(m.child2, kep, level+1) + +def print_multiple_simple(node, kep): + for level, x in node.iter_levels(): + output = '' + if level == 0: output += 'Multiple ' + output += ' ' * level + particle = x + output += "{0} id = {1} mass = {2} radius = {3}".format(particle.key, + particle.id, + particle.mass.number, + particle.radius.number) + if not particle.child1 is None: + child1 = particle.child1 + child2 = particle.child2 + M,a,e,r,E,t = get_component_binary_elements(child1, child2, kep) + mu = child1.mass*child2.mass/M + output += " semi = {0} energy = {1}".format(a.number, + (mu*E).number) + print(output) + sys.stdout.flush() + +def print_multiple_detailed(node, kep, pre, kT, dcen): + is_bin = 1 + Etot = zero + for level, x in node.iter_levels(): + particle = x + init = pre + if level == 0: init += 'Multiple ' + init += ' ' * level + id = particle.id + M = particle.mass.number + if not particle.child1 is None: + if level > 0: is_bin = 0 + child1 = particle.child1 + child2 = particle.child2 + idlow = min(child1.id, child2.id) + idhigh = max(child1.id, child2.id) + print('%s%d (%d,%d) m=%.5f' % (init, id, idlow, idhigh, M), end=' ') + sys.stdout.flush() + M,a,e,r,Emu,t = get_component_binary_elements(child1, child2, kep) + cm = (child1.mass*child1.position + child2.mass*child2.position)/M + mu = child1.mass*child2.mass/M + E = Emu*mu + Etot += E + D = 0. + for k in range(3): + D += (cm[k].number - dcen[k].number)**2 + D = numpy.sqrt(D) + print('a=%.6f e=%4f r=%6f D=%.4f E/mu=%.5f E=%.5f E/kT=%.5f' % \ + (a.number, e, r.number, D, Emu.number, E.number, E/kT)) + sys.stdout.flush() + else: + print('%s%d m=%.5f' % (init, id, M)) + sys.stdout.flush() + + return is_bin, Etot + +def print_top_level(nodes, G): + + # Print various top-level quantities of interest during rescaling. + + print('') + print('distances:') + for i in nodes: + print(i.id, ' ', end=' ') + for j in nodes: + if j.id != i.id: + rij = (j.position-i.position).length() + print(j.id, rij, ' ', end=' ') + print('') + + print('radial velocities:') + for i in nodes: + print(i.id, ' ', end=' ') + for j in nodes: + if j.id != i.id: + rij = (j.position-i.position).length() + vdotr = ((j.velocity-i.velocity)*(j.position-i.position)).sum() + print(j.id, vdotr/rij, ' ', end=' ') + print('') + + print('potentials:') + for i in nodes: + print(i.id, ' ', end=' ') + mi = i.mass + for j in nodes: + if j.id != i.id: + mj = j.mass + rij = (j.position-i.position).length() + print(j.id, -G*mi*mj/rij, ' ', end=' ') + + print('') + + print('energies:') + pot = 0.0 + kin = 0.0 + for i in nodes: + print(i.id, ' ', end=' ') + mi = i.mass + vi = i.velocity + kin += 0.5*mi*(vi**2).sum() + for j in nodes: + if j.id != i.id: + mj = j.mass + muij = mi*mj/(mi+mj) + rij = (j.position-i.position).length() + vij2 = (j.velocity-i.velocity).length_squared() + print(j.id, 0.5*muij*vij2 - mi*mj/rij, ' ', end=' ') + if j.id > i.id: + pot -= G*mi*mj/rij + + print('') + print('totals:', pot, kin, -kin/pot, pot+kin) + print('') diff --git a/src/tycho/multiples2.pyc b/src/tycho/multiples2.pyc deleted file mode 100644 index c1a0224a4a14bb3c5bdbd9e6821b1ca3f9e72a86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49595 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 aT_at4yPoLU+4V?Q2NzfF>-x5?d;fpaxa4gB diff --git a/src/tycho/multiples3.pyc b/src/tycho/multiples3.pyc deleted file mode 100644 index df16040441c3e693e4b21f1051809083f6f466c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49761 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 From aaff4ccf148d10cdd6ae562468a513673a66bf9b Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 12 Aug 2020 16:24:41 -0400 Subject: [PATCH 052/130] Fixed syntax error. --- sim_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim_cluster.py b/sim_cluster.py index 366ec37..121c468 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -101,7 +101,7 @@ def log_encounter(self, time, particles_in_encounter): _temp.time = time # Seperate out Stars to Nab Keys for EncounterDictionary Logging - enc_stars = _temp[_temp > self.limiting_mass_for_planets] + enc_stars = _temp[_temp.mass > self.limiting_mass_for_planets] IDs_of_StarsInEncounter = [star.id for star in enc_stars if star.id < 1000000] for star_ID in IDs_of_StarsInEncounter: self.encounterDict[star_ID].append(_temp) From 19c0e36a68f177f28cf42f427d2baaa7a6d0280c Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 12 Aug 2020 16:27:30 -0400 Subject: [PATCH 053/130] Fix syntax. --- sim_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim_cluster.py b/sim_cluster.py index 121c468..705b67b 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -105,7 +105,7 @@ def log_encounter(self, time, particles_in_encounter): IDs_of_StarsInEncounter = [star.id for star in enc_stars if star.id < 1000000] for star_ID in IDs_of_StarsInEncounter: self.encounterDict[star_ID].append(_temp) - if debug_mode > 0: + if self.debug_mode > 0: enc_planets = _temp[_temp.mass <= limiting_mass_for_planets] print("Stars:", enc_stars.id) print("Planets:", enc_planets.id) From bc72631275ddf4617113139909fd37dd69c2174d Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 12 Aug 2020 16:30:15 -0400 Subject: [PATCH 054/130] Syntax fix. --- sim_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim_cluster.py b/sim_cluster.py index 705b67b..9cd9393 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -106,7 +106,7 @@ def log_encounter(self, time, particles_in_encounter): for star_ID in IDs_of_StarsInEncounter: self.encounterDict[star_ID].append(_temp) if self.debug_mode > 0: - enc_planets = _temp[_temp.mass <= limiting_mass_for_planets] + enc_planets = _temp[_temp.mass <= self.limiting_mass_for_planets] print("Stars:", enc_stars.id) print("Planets:", enc_planets.id) print("Keys Set for EncounterDictionary:", IDs_of_StarsInEncounter) From 476e4362802f5a155d5334765b9e070ee0beecbb Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 12 Aug 2020 20:13:35 -0400 Subject: [PATCH 055/130] Added heapy debugging. --- sim_cluster.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/sim_cluster.py b/sim_cluster.py index 9cd9393..1affb08 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -13,6 +13,8 @@ from optparse import OptionParser import glob +from guppy import hpy + # Tyler's imports import hashlib import copy @@ -406,7 +408,7 @@ def move_particle(set_from, set_to, particle_id): gravity_constant=units.constants.G) multiples_code.neighbor_perturbation_limit = 0.05 multiples_code.neighbor_veto = True - multiples_code.global_debug = 2 + multiples_code.global_debug = 0 # ---------------------------------------------------------------------------------------------------- # Setting up Stellar Evolution Code (SeBa) @@ -460,7 +462,7 @@ def move_particle(set_from, set_to, particle_id): # Sets as Encounters are Detected encounter_file = None EH = EncounterHandler() - EH.debug_mode = 1 + EH.debug_mode = 0 multiples_code.encounterLogger = EH.log_encounter snapshots_dir = os.getcwd()+"/Snapshots" @@ -513,6 +515,8 @@ def move_particle(set_from, set_to, particle_id): #increase_index = False #timestep_reset = False + hp = hpy() + before = hp.heap() while t_current <= t_end: ## Artificially Evolve the Cluster to Get Multiples to Pickup Planetary Systems & Binaries #if t_current >= t_start and t_current <= t_catch: @@ -529,7 +533,7 @@ def move_particle(set_from, set_to, particle_id): # #gravity_code.parameters.timestep_parameter = 2**(-5) # gravity_code.parameters.force_sync = False # timestep_reset = True - + hp.heap() # Evolve the Gravitational Codes ( via Bridge Code) bridge_code.evolve_model(t_current) @@ -595,6 +599,11 @@ def move_particle(set_from, set_to, particle_id): print('-------------\n') sys.stdout.flush() + if step_index == 1000: + after = hp.heap() + leftover = after - before + import pdb; pdb.set_trace() + # Increase the Step Index #if increase_index: step_index += 1 From 694390a068a694133dccad16d438e6f50ada41e6 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 12 Aug 2020 20:17:20 -0400 Subject: [PATCH 056/130] Changed occurance --- sim_cluster.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sim_cluster.py b/sim_cluster.py index 1affb08..b534396 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -533,7 +533,6 @@ def move_particle(set_from, set_to, particle_id): # #gravity_code.parameters.timestep_parameter = 2**(-5) # gravity_code.parameters.force_sync = False # timestep_reset = True - hp.heap() # Evolve the Gravitational Codes ( via Bridge Code) bridge_code.evolve_model(t_current) @@ -599,7 +598,7 @@ def move_particle(set_from, set_to, particle_id): print('-------------\n') sys.stdout.flush() - if step_index == 1000: + if step_index%500 == 0 and step_index != 0: after = hp.heap() leftover = after - before import pdb; pdb.set_trace() From 22dc23d9c50ebaddaef4f2d3dbfd3682fac66a9b Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 12 Aug 2020 21:27:45 -0400 Subject: [PATCH 057/130] Fixed PDB call --- sim_cluster.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sim_cluster.py b/sim_cluster.py index b534396..bd4355f 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -601,6 +601,8 @@ def move_particle(set_from, set_to, particle_id): if step_index%500 == 0 and step_index != 0: after = hp.heap() leftover = after - before + sys.stdout = orig_stdout + sys.stdout.flush() import pdb; pdb.set_trace() # Increase the Step Index From a38da4a757aa58bb340248a9dbee29bcea500dc4 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 12 Aug 2020 22:06:34 -0400 Subject: [PATCH 058/130] Temporarily turrning off log. --- sim_cluster.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sim_cluster.py b/sim_cluster.py index bd4355f..f4677dd 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -449,8 +449,8 @@ def move_particle(set_from, set_to, particle_id): # Piping all Terminal Output to the Log File orig_stdout = sys.stdout - f = open("%s_%s.log" %(cluster_name, tp.strftime("%y%m%d", tp.gmtime())), 'w') - sys.stdout = f + #f = open("%s_%s.log" %(cluster_name, tp.strftime("%y%m%d", tp.gmtime())), 'w') + #sys.stdout = f sys.stdout.flush() # Writing the Initial Conditions & Particle Sets @@ -601,7 +601,7 @@ def move_particle(set_from, set_to, particle_id): if step_index%500 == 0 and step_index != 0: after = hp.heap() leftover = after - before - sys.stdout = orig_stdout + #sys.stdout = orig_stdout sys.stdout.flush() import pdb; pdb.set_trace() From e6468989425052c11c6636fb49d925270f486c53 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 12 Aug 2020 22:20:47 -0400 Subject: [PATCH 059/130] Added custom printing strategy for units. --- sim_cluster.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sim_cluster.py b/sim_cluster.py index f4677dd..1691491 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -57,6 +57,9 @@ from tycho import multiples as multiples #import amuse.couple.multiples as multiples +set_printing_strategy("custom", preferred_units = [units.MSun, units.AU, units.day, units.deg], \ + precision = 6, prefix = "", separator = "[", suffix = "]") + # ------------------------------------- # # Required Non-Seperable Functions # From 7ba9e445aeebf89a6e015568e91578aa1ad974c9 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 12 Aug 2020 22:33:55 -0400 Subject: [PATCH 060/130] Update travis.yml for guppy3 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d1ab076..c445312 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ install: - pip install rebound - pip install amuse-framework - pip install amuse-ph4 amuse-kepler amuse-sse amuse-seba amuse-smalln + - pip install guppy3 script: - python sim_cluster.py -g -p 5 -s 10 -c Leonis -w 4.5 -T 11 -S Leonis -b From f3d4889996900abbb56d8f1b019bba04d29b3f42 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 13 Aug 2020 15:30:09 -0400 Subject: [PATCH 061/130] Adding in supernova natal kick. --- sim_cluster.py | 11 ++++++++--- src/tycho/util.py | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/sim_cluster.py b/sim_cluster.py index 1691491..30692c9 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -195,7 +195,7 @@ def move_particle(set_from, set_to, particle_id): help="Enter the number of planetary systems desired.") parser.add_option("-s", "--num-stars", dest="num_stars", default=64, type="int", help="Enter the number of stars desired.") - parser.add_option("-t", "--timestep", dest="dt", default=0.002, type="float", + parser.add_option("-t", "--timestep", dest="dt", default=0.2, type="float", help="Enter the Top-Level Timestep in Myr.") parser.add_option("-c", "--cluster-name", dest="cluster_name", default=None, type="str", help="Enter the name of the cluster (Defaults to Numerical Naming Scheme).") @@ -417,6 +417,8 @@ def move_particle(set_from, set_to, particle_id): # Setting up Stellar Evolution Code (SeBa) sev_code = SeBa() sev_code.particles.add_particles(Stellar_Bodies) + supernova_detection = sev_code.stopping_conditions.supernova_detection + supernova_detection.enable() # ---------------------------------------------------------------------------------------------------- @@ -486,9 +488,11 @@ def move_particle(set_from, set_to, particle_id): elif not crash: t_start = 10 | units.Myr # Average for Age of After Gas Ejection sev_code.evolve_model(t_start) + util.resolve_supernova(supernova_detection, Stellar_Bodies, t_start) + channel_from_sev_to_stellar.copy_attributes(["mass", "luminosity", "stellar_type", "temperature", "age"]) - channel_from_gravitating_to_multi.copy_attributes(["mass"]) + channel_from_gravitating_to_multi.copy_attributes(["mass", "vx", "vy", "vz"]) # Ensuring that Multiples Picks up All Desired Systems gravity_code.parameters.begin_time = t_start @@ -553,13 +557,14 @@ def move_particle(set_from, set_to, particle_id): # Evolve the Stellar Codes (via SEV Code with Channels) # TODO: Ensure Tight Binaries are Evolved Correctly (See Section 3.2.8) sev_code.evolve_model(t_current) + util.resolve_supernova(supernova_detection, Stellar_Bodies, t_current) # Sync the Stellar Code w/ the "Stellar_Bodies" Superset channel_from_sev_to_stellar.copy_attributes(["mass", "luminosity", "stellar_type", "temperature", "age"]) # Sync the Multiples Particle Set's Masses to the Stellar_Bodies' Masses - channel_from_gravitating_to_multi.copy_attributes(["mass"]) + channel_from_gravitating_to_multi.copy_attributes(["mass", "vx", "vy", "vz"]) # Note: The "mass" Attribute in "Gravitating_Bodies" is synced when "Stellar_Bodies" is. if step_index == 5: diff --git a/src/tycho/util.py b/src/tycho/util.py index 06c2dab..41e509e 100644 --- a/src/tycho/util.py +++ b/src/tycho/util.py @@ -274,3 +274,27 @@ def get_stellar_radius(star): print(sev_code.particles[0].radius) sev_code.stop() return radius + +def resolve_supernova(supernova_detection, bodies, time): + # Drawn from gravity_stellar_eventdriven.py in AMUSE Textbook + if supernova_detection.is_set(): + print("At time=", time.in_(units.Myr), \ + len(supernova_detection.particles(0)), 'supernova(e) detected') + + Nsn = 0 + for ci in range(len(supernova_detection.particles(0))): + print(supernova_detection.particles(0)) + particles_in_supernova \ + = Particles(particles=supernova_detection.particles(0)) + natal_kick_x = particles_in_supernova.natal_kick_x + natal_kick_y = particles_in_supernova.natal_kick_y + natal_kick_z = particles_in_supernova.natal_kick_z + + particles_in_supernova \ + = particles_in_supernova.get_intersecting_subset_in(bodies) + particles_in_supernova.vx += natal_kick_x + particles_in_supernova.vy += natal_kick_y + particles_in_supernova.vz += natal_kick_z + Nsn += 1 + + print('Resolved', Nsn, 'supernova(e)') From f631de13d3769652fb980f4aa1217ff5b266bb6f Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 13 Aug 2020 15:54:27 -0400 Subject: [PATCH 062/130] Adding in profile decorator. --- sim_cluster.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sim_cluster.py b/sim_cluster.py index 30692c9..fc0f7a0 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -93,7 +93,8 @@ def __init__(self): self.encounterDict = defaultdict(list) self.debug_mode = 0 self.limiting_mass_for_planets = 13 | units.MJupiter - + + @profile def log_encounter(self, time, particles_in_encounter): # Initialize the Temporary Particle Set to Ensure Nothing # Changes inside "particles_in_encounter" From e2ac4c15719ff1dddb01a1df97ab9b75956a1700 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 13 Aug 2020 16:13:20 -0400 Subject: [PATCH 063/130] Update travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c445312..03fe6f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ install: - pip install amuse-framework - pip install amuse-ph4 amuse-kepler amuse-sse amuse-seba amuse-smalln - pip install guppy3 + - pip install -U memory_profiler script: - python sim_cluster.py -g -p 5 -s 10 -c Leonis -w 4.5 -T 11 -S Leonis -b From 07e8730c267ab64b5bc310fd4abfa951da0525b7 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 13 Aug 2020 20:43:09 -0400 Subject: [PATCH 064/130] Added in new cluster parameters. --- sim_cluster.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/sim_cluster.py b/sim_cluster.py index fc0f7a0..ca88064 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -57,7 +57,7 @@ from tycho import multiples as multiples #import amuse.couple.multiples as multiples -set_printing_strategy("custom", preferred_units = [units.MSun, units.AU, units.day, units.deg], \ +set_printing_strategy("custom", preferred_units = [units.MSun, units.AU, units.Myr, units.deg], \ precision = 6, prefix = "", separator = "[", suffix = "]") @@ -93,7 +93,7 @@ def __init__(self): self.encounterDict = defaultdict(list) self.debug_mode = 0 self.limiting_mass_for_planets = 13 | units.MJupiter - + @profile def log_encounter(self, time, particles_in_encounter): # Initialize the Temporary Particle Set to Ensure Nothing @@ -214,12 +214,15 @@ def move_particle(set_from, set_to, particle_id): help = "Enables loading a pregenerated HDF5 file in the Execution Directory.") parser.add_option("-N", "--grav_workers", dest="grav_workers", default=1, type="float", help="Enter the desired number of PH4 workers.") + parser.add_option("-r", "--virial_radius", dest="vrad", default=2, type="float") + parser.add_options("-D", "--galactic_dist", dest="galactic_dist" default = 9, type="float") (options, args) = parser.parse_args() # Set Commonly Used Python Variables from Options num_stars = options.num_stars num_psys = options.num_psys w0 = options.w0 + vrad = options.vrad | units.pc t_start = 0.0 | units.Myr t_end = options.t_end | units.Myr delta_t = options.dt | units.Myr @@ -275,11 +278,11 @@ def move_particle(set_from, set_to, particle_id): # Generate a New Cluster Matching Desired Initial Conditions & the Large-Scale Converter if doBinaries: Starting_Stars, LargeScaleConverter, Binary_CoMs, Binary_Singles = \ - create.king_cluster_v2(num_stars, w0 = w0, do_binaries = True, + create.king_cluster_v2(num_stars, vradius = vrad, w0 = w0, do_binaries = True, split_binaries = True, seed = options.seed) else: Starting_Stars, LargeScaleConverter = \ - create.king_cluster_v2(num_stars, w0 = w0, do_binaries = False, seed = options.seed) + create.king_cluster_v2(num_stars, vradius = vrad, w0 = w0, do_binaries = False, seed = options.seed) # Create Initial Conditions Array initial_conditions = util.store_ic(LargeScaleConverter, options) @@ -350,7 +353,7 @@ def move_particle(set_from, set_to, particle_id): # Setting up Galactic Potential Code (MGalaxy) galactic_code = MWpotentialBovy2015() # Moving Gravitating_Bodies into a Circular Orbit Around Galactic Core - rinit_from_galactic_core = 9.0 | units.kpc + rinit_from_galactic_core = options.galactic_dist | units.kpc vcircular = galactic_code.circular_velocity(rinit_from_galactic_core) Gravitating_Bodies.x += rinit_from_galactic_core Gravitating_Bodies.vy += vcircular From a30a49ed10bdb6ce70eee1925ca534d0df0030f1 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 13 Aug 2020 21:07:58 -0400 Subject: [PATCH 065/130] Added in additional options for better write performance. --- sim_cluster.py | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/sim_cluster.py b/sim_cluster.py index ca88064..aae0e0b 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -94,7 +94,6 @@ def __init__(self): self.debug_mode = 0 self.limiting_mass_for_planets = 13 | units.MJupiter - @profile def log_encounter(self, time, particles_in_encounter): # Initialize the Temporary Particle Set to Ensure Nothing # Changes inside "particles_in_encounter" @@ -196,7 +195,7 @@ def move_particle(set_from, set_to, particle_id): help="Enter the number of planetary systems desired.") parser.add_option("-s", "--num-stars", dest="num_stars", default=64, type="int", help="Enter the number of stars desired.") - parser.add_option("-t", "--timestep", dest="dt", default=0.2, type="float", + parser.add_option("-t", "--timestep", dest="dt", default=2**-3, type="float", help="Enter the Top-Level Timestep in Myr.") parser.add_option("-c", "--cluster-name", dest="cluster_name", default=None, type="str", help="Enter the name of the cluster (Defaults to Numerical Naming Scheme).") @@ -216,11 +215,14 @@ def move_particle(set_from, set_to, particle_id): help="Enter the desired number of PH4 workers.") parser.add_option("-r", "--virial_radius", dest="vrad", default=2, type="float") parser.add_options("-D", "--galactic_dist", dest="galactic_dist" default = 9, type="float") + parser.add_option("-i", "--interactive_debug", dest="debug", action="store_true", + help = "Enables interactive debugging.") (options, args) = parser.parse_args() # Set Commonly Used Python Variables from Options num_stars = options.num_stars num_psys = options.num_psys + doDebug = options.debug w0 = options.w0 vrad = options.vrad | units.pc t_start = 0.0 | units.Myr @@ -458,8 +460,9 @@ def move_particle(set_from, set_to, particle_id): # Piping all Terminal Output to the Log File orig_stdout = sys.stdout - #f = open("%s_%s.log" %(cluster_name, tp.strftime("%y%m%d", tp.gmtime())), 'w') - #sys.stdout = f + if not doDebug: + f = open("%s_%s.log" %(cluster_name, tp.strftime("%y%m%d", tp.gmtime())), 'w') + sys.stdout = f sys.stdout.flush() # Writing the Initial Conditions & Particle Sets @@ -526,24 +529,13 @@ def move_particle(set_from, set_to, particle_id): #increase_index = False #timestep_reset = False - hp = hpy() - before = hp.heap() + write_out_step = np.floor(5 | units.Myr)/delta_t) + + if doDebug: + hp = hpy() + before = hp.heap() while t_current <= t_end: - ## Artificially Evolve the Cluster to Get Multiples to Pickup Planetary Systems & Binaries - #if t_current >= t_start and t_current <= t_catch: - # bridge_code.timestep = dt_small - # t_current += dt_small - # #gravity_code.parameters.timestep_parameter = 2**(-5) - # gravity_code.parameters.force_sync = True - ## Increase the Current Time by the Normal Time-Step - #else: t_current += delta_t - # increase_index = True - #if increase_index and not timestep_reset: - # bridge_code.timestep = delta_t - # #gravity_code.parameters.timestep_parameter = 2**(-5) - # gravity_code.parameters.force_sync = False - # timestep_reset = True # Evolve the Gravitational Codes ( via Bridge Code) bridge_code.evolve_model(t_current) @@ -575,7 +567,7 @@ def move_particle(set_from, set_to, particle_id): E0_1 = print_diagnostics(multiples_code) # Write out the "Gravitating_Bodies" Superset Every 5 Time-Steps - if step_index%5 == 0: + if step_index%write_out_step == 0: snapshot_s_filename = snapshots_s_dir+"/"+cluster_name+"_stars_t%.3f.hdf5" %(t_current.number) write_set_to_file(Individual_Stars, snapshot_s_filename, format="hdf5", close_file=True, version=2) snapshot_p_filename = snapshots_p_dir+"/"+cluster_name+"_planets_t%.3f.hdf5" %(t_current.number) @@ -610,7 +602,7 @@ def move_particle(set_from, set_to, particle_id): print('-------------\n') sys.stdout.flush() - if step_index%500 == 0 and step_index != 0: + if step_index%500 == 0 and step_index != 0 and doDebug: after = hp.heap() leftover = after - before #sys.stdout = orig_stdout @@ -637,7 +629,8 @@ def move_particle(set_from, set_to, particle_id): print_diagnostics(multiples_code, E0_1) sys.stdout.flush() - sys.stdout = orig_stdout + if not doDebug: + sys.stdout = orig_stdout print('\n[UPDATE] Run Finished at %s! \n' %(tp.strftime("%Y/%m/%d-%H:%M:%S", tp.gmtime()))) print_diagnostics(multiples_code, E0) print_diagnostics(multiples_code, E0_1) From 13d7801f45087da5c2501facc47f5ae31839de62 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 13 Aug 2020 21:30:08 -0400 Subject: [PATCH 066/130] Syntax fix. --- sim_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim_cluster.py b/sim_cluster.py index aae0e0b..7738a8e 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -214,7 +214,7 @@ def move_particle(set_from, set_to, particle_id): parser.add_option("-N", "--grav_workers", dest="grav_workers", default=1, type="float", help="Enter the desired number of PH4 workers.") parser.add_option("-r", "--virial_radius", dest="vrad", default=2, type="float") - parser.add_options("-D", "--galactic_dist", dest="galactic_dist" default = 9, type="float") + parser.add_options("-D", "--galactic_dist", dest="galactic_dist", default = 9, type="float") parser.add_option("-i", "--interactive_debug", dest="debug", action="store_true", help = "Enables interactive debugging.") (options, args) = parser.parse_args() From 6d0d200e789a77cf458eaf5d244019335e8650f1 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 13 Aug 2020 21:40:28 -0400 Subject: [PATCH 067/130] Syntax fix. --- sim_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim_cluster.py b/sim_cluster.py index 7738a8e..a718983 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -529,7 +529,7 @@ def move_particle(set_from, set_to, particle_id): #increase_index = False #timestep_reset = False - write_out_step = np.floor(5 | units.Myr)/delta_t) + write_out_step = np.floor((5 | units.Myr)/delta_t) if doDebug: hp = hpy() From adb5eefec88167fdb7bc13d2fcb3f7b41470eed5 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 13 Aug 2020 21:53:49 -0400 Subject: [PATCH 068/130] Syntax fix. --- sim_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim_cluster.py b/sim_cluster.py index a718983..7bb2414 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -214,7 +214,7 @@ def move_particle(set_from, set_to, particle_id): parser.add_option("-N", "--grav_workers", dest="grav_workers", default=1, type="float", help="Enter the desired number of PH4 workers.") parser.add_option("-r", "--virial_radius", dest="vrad", default=2, type="float") - parser.add_options("-D", "--galactic_dist", dest="galactic_dist", default = 9, type="float") + parser.add_option("-D", "--galactic_dist", dest="galactic_dist", default = 9, type="float") parser.add_option("-i", "--interactive_debug", dest="debug", action="store_true", help = "Enables interactive debugging.") (options, args) = parser.parse_args() From 18c64d60c7e9bf9610ff945b823dcf55a225865b Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 13 Aug 2020 21:56:58 -0400 Subject: [PATCH 069/130] Syntax fix. --- sim_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim_cluster.py b/sim_cluster.py index 7bb2414..13c6982 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -215,7 +215,7 @@ def move_particle(set_from, set_to, particle_id): help="Enter the desired number of PH4 workers.") parser.add_option("-r", "--virial_radius", dest="vrad", default=2, type="float") parser.add_option("-D", "--galactic_dist", dest="galactic_dist", default = 9, type="float") - parser.add_option("-i", "--interactive_debug", dest="debug", action="store_true", + parser.add_option("-I", "--interactive_debug", dest="debug", action="store_true", help = "Enables interactive debugging.") (options, args) = parser.parse_args() From d0584a848e2bb6e4baa2469ad16fbb1a0e4b8c97 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Fri, 14 Aug 2020 21:57:50 -0400 Subject: [PATCH 070/130] Syntax fix. --- cut_encounters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cut_encounters.py b/cut_encounters.py index ca2b865..6709783 100644 --- a/cut_encounters.py +++ b/cut_encounters.py @@ -158,7 +158,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): temp = stellar_systems.get_heirarchical_systems_from_set(encounter_db[star_ID][0]) print(temp) if len(temp.keys()) <= 1: - print(encounter_db[starID][0].id) + print(encounter_db[star_ID][0].id) del encounter_db[star_ID] print("After Removal of Just Initializations", len(encounter_db.keys())) for star_ID in list(encounter_db.keys()): From 0159d0b43412daddc1aa392c4967a199e12a8f77 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Sat, 15 Aug 2020 11:08:16 -0400 Subject: [PATCH 071/130] Syntax fix in write_set. --- gen_RandEncounters.py | 2 +- sim_cluster.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gen_RandEncounters.py b/gen_RandEncounters.py index 6bc5ec0..597f67c 100644 --- a/gen_RandEncounters.py +++ b/gen_RandEncounters.py @@ -74,7 +74,7 @@ def gen_scatteringIC(encounter_db, doMultipleClusters=False): continue # Remove Jupiter and Add Desired Planetary System enc_bodies = replace_planetary_system(encounter.copy(), kepler_workers=kepler_workers) - write_set_to_file(enc_bodies.savepoint(0 | units.Myr), output_HDF5File, 'hdf5', version='2.0') + write_set_to_file(enc_bodies, output_HDF5File, 'hdf5', version='2.0', close_file=True) printID = str(star_ID)+"-"+str(encounter_ID)+"-"+str(rotation_ID) print(util.timestamp(), "Finished Generating Random Encounter ID:", printID, "...") rotation_ID += 1 diff --git a/sim_cluster.py b/sim_cluster.py index 13c6982..88f3d34 100644 --- a/sim_cluster.py +++ b/sim_cluster.py @@ -569,9 +569,9 @@ def move_particle(set_from, set_to, particle_id): # Write out the "Gravitating_Bodies" Superset Every 5 Time-Steps if step_index%write_out_step == 0: snapshot_s_filename = snapshots_s_dir+"/"+cluster_name+"_stars_t%.3f.hdf5" %(t_current.number) - write_set_to_file(Individual_Stars, snapshot_s_filename, format="hdf5", close_file=True, version=2) + write_set_to_file(Individual_Stars, snapshot_s_filename, format="hdf5", close_file=True, version='2.0') snapshot_p_filename = snapshots_p_dir+"/"+cluster_name+"_planets_t%.3f.hdf5" %(t_current.number) - write_set_to_file(Planets, snapshot_p_filename, format="hdf5", close_file=True, version=2) + write_set_to_file(Planets, snapshot_p_filename, format="hdf5", close_file=True, version='2.0') # TODO: Write out a Crash File Every 50 Time-Steps #crash_base = "CrashSave/"+cluster_name+"_time_"+t_current.in_(units.Myr) From cd788a56eec35547e945242ffedd41b6aaddd159 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 18 Aug 2020 14:35:35 -0400 Subject: [PATCH 072/130] Overhaul of scattering. --- sim_encounters.py | 184 ++++++++++------------------------------ src/tycho/scattering.py | 41 +++++++-- 2 files changed, 77 insertions(+), 148 deletions(-) diff --git a/sim_encounters.py b/sim_encounters.py index 6fd4b57..aa496e6 100644 --- a/sim_encounters.py +++ b/sim_encounters.py @@ -31,7 +31,7 @@ from amuse.datamodel import particle_attributes from amuse.io import * from amuse.lab import * -from tycho import util +from tycho import util, scattering # Import the Amuse Gravity & Close-Encounter Packages from amuse.community.smalln.interface import SmallN @@ -75,7 +75,7 @@ def mpScatterExperiments(list_of_clusterDirs, desiredFunction): th.start() job_queue.join() -def do_all_scatters_for_single_cluster(rootExecDir, **kwargs): +def simulate_all_close_encounters(rootExecDir, **kwargs): ''' This function will run all scatters for a single cluster in serial. str rootExecDir -> The absolute root directory for all single cluster files. @@ -90,146 +90,52 @@ def do_all_scatters_for_single_cluster(rootExecDir, **kwargs): # Define the Cluster's Name cluster_name = rootExecDir.split("/")[-1] # Generate List of Scattering IC HDF5 Paths - paths_of_IC_files = glob.glob(rootExecDir+'/Scatter_IC/*/*.hdf5') + enc_dict = scattering.build_ClusterEncounterHistory(rootExecDir) # Find all Primary Star IDs - star_IDs = [path.split("/")[-2] for path in paths_of_IC_files] # '1221' + star_IDs = enc_dict.keys() # Integer tied to StarID # Set Up Output Directory Structure output_MainDirectory = rootExecDir+"/Encounters" - if not os.path.exists(output_MainDirectory): os.mkdir(output_MainDirectory) + if not os.path.exists(output_MainDirectory): + os.mkdir(output_MainDirectory) + # Initialize the Necessary Worker Lists + converter = nbody_system.nbody_to_si(1 | units.MSun, 100 |units.AU) + KepW = [] + for i in range(2): + KepW.append(Kepler(unit_converter = converter, redirection = 'none')) + KepW[-1].initialize_code() + NBodyW = [initialize_GravCode(ph4), initialize_isOverCode()] + SecW = SecularMultiple() + + # Loop Over the Stars for star_ID in star_IDs: - output_KeyDirectory = output_MainDirectory+"/"+star_ID - if not os.path.exists(output_KeyDirectory): os.mkdir(output_KeyDirectory) - for i, path_of_IC in enumerate(paths_of_IC_files): - itteration_filename = path_of_IC.split('/')[-1] # 'Enc-0_Rot_0.hdf5' - enc_bodies = read_set_from_file(path_of_IC, format="hdf5", version='2.0', close_file=True) - output_HDF5File = output_MainDirectory+"/"+star_IDs[i]+"/"+itteration_filename - print(output_HDF5File) - if not os.path.exists(output_HDF5File): - run_collision(enc_bodies, max_runtime, delta_time, output_HDF5File, GCodes=GCodes, doEncPatching=False) - else: - print(util.timestamp(), "Skipping", itteration_filename.split(".hdf5")[0], "of system", star_IDs[i]) - -def initialize_GravCode(desiredCode, **kwargs): - converter = kwargs.get("converter", None) - n_workers = kwargs.get("number_of_workers", 1) - if converter == None: - converter = nbody_system.nbody_to_si(1 | units.MSun, 100 |units.AU) - GCode = desiredCode(number_of_workers = n_workers, redirection = "none", convert_nbody = converter) - GCode.initialize_code() - GCode.parameters.set_defaults() - if desiredCode == ph4: - GCode.parameters.timestep_parameter = 2.0**(-4.0) - if desiredCode == SmallN: - GCode.parameters.timestep_parameter = 0.05 - return GCode - -def initialize_isOverCode(**kwargs): - converter = kwargs.get("converter", None) - if converter == None: - converter = nbody_system.nbody_to_si(1 | units.MSun, 100 |units.AU) - isOverCode = SmallN(redirection = "none", convert_nbody = converter) - isOverCode.initialize_code() - isOverCode.parameters.set_defaults() - isOverCode.parameters.allow_full_unperturbed = 0 - return isOverCode - -def run_collision(bodies, end_time, delta_time, save_file, **kwargs): - # Define Additional User Options and Set Defaults Properly - converter = kwargs.get("converter", None) - doEncPatching = kwargs.get("doEncPatching", False) - doVerboseSaves = kwargs.get("doVerboseSaves", False) - GCodes = kwargs.get("GCodes", None) - # Set Up the Integrators - if GCodes == None: - if converter == None: - converter = nbody_system.nbody_to_si(bodies.mass.sum(), 2 * np.max(bodies.radius.number) | bodies.radius.unit) - gravity = initialize_GravCode(ph4, converter=converter) - over_grav = initialize_isOverCode(converter=converter) - else: - gravity = GCodes[0] - over_grav = GCodes[1] - # Storing Initial Center of Mass Information for the Encounter - rCM_i = bodies.center_of_mass() - vCM_i = bodies.center_of_mass_velocity() - GravitatingBodies = Particles() - for body in bodies: - GravitatingBodies.add_particle(body.copy()) - # Fixing Stored Encounter Particle Set to Feed into SmallN - #GravitatingBodies = Particles(particles=GravitatingBodies) - if 'child1' in GravitatingBodies.get_attribute_names_defined_in_store(): - del GravitatingBodies.child1, GravitatingBodies.child2 - # Moving the Encounter's Center of Mass to the Origin and Setting it at Rest - GravitatingBodies.position -= rCM_i - GravitatingBodies.velocity -= vCM_i - # Add and Commit the Scattering Particles - gravity.particles.add_particles(GravitatingBodies) # adds bodies to gravity calculations - gravity.commit_particles() - # Create the Channel to Python Set & Copy it Over - channel_from_grav_to_python = gravity.particles.new_channel_to(GravitatingBodies) - channel_from_grav_to_python.copy() - # Get Free-Fall Time for the Collision - s = util.get_stars(GravitatingBodies) - t_freefall = s.dynamical_timescale() - # Setting Coarse Timesteps - list_of_times = np.arange(0., end_time, delta_time) | units.yr - stepNumber = 0 - # Integrate the Encounter Until Over ... - for current_time in list_of_times: - # Evolve the Model to the Desired Current Time - gravity.evolve_model(current_time) - # Update Python Set in In-Code Set - channel_from_grav_to_python.copy() # original - channel_from_grav_to_python.copy_attribute("index_in_code", "id") - # Handle Writing Output of Integration - if doVerboseSaves: - # Write a Save Every Coarse Timestep - write_set_to_file(GravitatingBodies.savepoint(current_time), save_file, 'hdf5', version='2.0') - else: - # Write a Save at the Begninning, Middle & End Times - if stepNumber%25 == 0: - # Write Set to File - gravity.particles.synchronize_to(GravitatingBodies) - write_set_to_file(GravitatingBodies.savepoint(current_time), save_file, 'hdf5', version='2.0') - # Check to See if the Encounter is Declared "Over" Every 50 Timesteps - if current_time > 1.25*t_freefall and stepNumber%25 == 0: #and len(list_of_times)/3.- stepNumber <= 0: - over = util.check_isOver(gravity.particles, over_grav) - print(over) - if over: - current_time += 100 | units.yr - # Get to a Final State After Several Planet Orbits - gravity.evolve_model(current_time) - # Update all Particle Sets - #gravity.update_particle_tree() - gravity.update_particle_set() - gravity.particles.synchronize_to(GravitatingBodies) - channel_from_grav_to_python.copy() - # Removes the Heirarchical Particle from the HDF5 File - # This is done for personal convience. - #for body in GravitatingBodies: - # if body.child1 != None or body.child1 != None: - # GravitatingBodies.remove_particle(body) - write_set_to_file(GravitatingBodies.savepoint(current_time), save_file, 'hdf5', version='2.0') - #print "Encounter has finished at Step #", stepNumber, '. Final Age:', current_time.in_(units.yr) - break - #else: - #print "Encounter has NOT finished at Step #", stepNumber - #t_freefall = util.get_stars(gravity.particles).dynamical_timescale() - stepNumber +=1 - if GCodes == None: - # Stop the Gravity Code Once the Encounter Finishes - gravity.stop() - over_grav.stop() - else: - gravity.reset() - over_grav.reset() - # Seperate out the Systems to Prepare for Encounter Patching - if doEncPatching: - - enc_patching.doEncPatching(GravitatingBodies) + try: + # Load the Close Encounter class for the Star + EncounterHandler = CloseEncounters(enc_dict[star_ID], KeplerWorkerList = KepW, \ + NBodyWorkerList = NBodyW, SecularWorker = SecW) + # Simulate Encounter + EncounterHandler.SimAllEncounters() + # Prepare Data for Pickling + file_name = output_MainDirectory+"/"+str(star_ID)+"_EncounterHandler.pk" + p_file = open(file_name, "wb") + # Remove Worker Lists from Class for Storage + EncounterHandler.kep = None + EncounterHandler.NBodyCodes = None + EncounterHandler.SecularCode = None + # Pickle EncounterHandler Class + # Note: This allows for ease-of-use when you want to revisit + # a specific star's simulation set in detail. + pickle.dump(EncounterHandler, p_file) + p_file.close() + # Note: Ensure the EncounterHandler class is deleted incase + # of a memory leak is possible in future updates. + del EncounterHandler + except: + print("Skipping StarID,", star_ID, "due to unforseen issues!") + pass + # Stop all Workers + for Worker in KepW+NBodyW+[SecW]: + Worker.stop() - else: - ResultingPSystems = GravitatingBodies - return ResultingPSystems # ------------------------------------- # # Defining Functions # @@ -290,10 +196,10 @@ def run_collision(bodies, end_time, delta_time, save_file, **kwargs): sys.stdout.flush() print(util.timestamp(), "Cluster", clusterDir.split("/")[-2], "has begun processing!") sys.stdout.flush() - do_all_scatters_for_single_cluster(clusterDir) + simulate_all_close_encounters(clusterDir) else: # Begin Looping Through Clusters (Each Cluster is a Queued Process) - mpScatterExperiments(all_clusterDirs, do_all_scatters_for_single_cluster) + mpScatterExperiments(all_clusterDirs, simulate_all_close_encounters) e_time = tp.time() diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 721a34b..5f02f9b 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -80,11 +80,13 @@ def __init__(self, Star_EncounterHistory, KeplerWorkerList = None, NBodyWorkerLi self.KeySystemID = int(Star_EncounterHistory[list(Star_EncounterHistory)[0]][0].split("/")[-2]) self.ICs = defaultdict(list) self.StartTimes = defaultdict(list) - self.desired_endtime = 2.0 | units.Gyr + self.desired_endtime = 1.0 | units.Gyr self.max_end_time = 0.1 | units.Myr self.kep = KeplerWorkerList self.NBodyCodes = NBodyWorkerList self.SecularCode = SecularWorker + self.getOEData = True + self.OEData = defaultdict(list) # Create a List of StartingTimes and Encounter Initial Conditions (ICs) for all Orientations for RotationKey in Star_EncounterHistory.keys(): for i, Encounter in enumerate(Star_EncounterHistory[RotationKey]): @@ -157,9 +159,9 @@ def SimAllEncounters(self): # Simulate System till the Next Encounter's Start Time Encounter_Inst = self.SingleEncounter(EndingState) - FinalState = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], \ + FinalState, data = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], \ start_time = EndingStateTime, \ - GCode = self.SecularCode) + GCode = self.SecularCode, getOEData=self.getOEData) # Begin Patching of the End State to the Next Encounter self.ICs[RotationKey][i+1] = self.PatchedEncounter(FinalState, NextEncounter) @@ -168,13 +170,27 @@ def SimAllEncounters(self): #print(CurrentEncounter[0].time.value_in(units.Myr)) #print(EndingState[0].time.value_in(units.Myr)) Encounter_Inst = self.SingleEncounter(EndingState) - FinalState = Encounter_Inst.SimSecularSystem(self.desired_endtime, \ + FinalState, data = Encounter_Inst.SimSecularSystem(self.desired_endtime, \ start_time = EndingStateTime, \ - GCode=self.SecularCode) + GCode = self.SecularCode, getOEData=self.getOEData) #print(FinalState[0].position) # Append the FinalState of Each Encounter to its Dictionary self.FinalStates[RotationKey].append(FinalState) + if self.getOEData and data != None: + self.OEData[RotationKey].append(data) + + # Stop the NBody Codes if not Provided + if self.kep == None: + self.kep[0].stop() + self.kep[1].stop() + # Start up NBodyCodes if Needed + if self.NBodyCodes == None: + self.NBodyCodes[0].stop() + self.NBodyCodes[1].stop() + # Start up SecularCode if Needed + if self.SecularCode == None: + self.SecularCode.stop() return None def PatchedEncounter(self, EndingState, NextEncounter, final_time): @@ -240,11 +256,18 @@ def __init__(self, EncounterBodies): def SimSecularSystem(self, desired_end_time, **kwargs): start_time = kwargs.get("start_time", 0 | units.Myr) + getOEData = kwargs.get("getOEData", False) GCode = kwargs.get("GCode", None) - self.particles = enc_patching.run_secularmultiple(self.particles, desired_end_time, \ - start_time = start_time, N_output=5, \ - GCode=GCode) - return self.particles + if getOEData: + self.particles, data = enc_patching.run_secularmultiple(self.particles, desired_end_time, \ + start_time = start_time, N_output=1, \ + GCode=GCode, exportData=getOEData) + return self.particles, data + else: + self.particles = enc_patching.run_secularmultiple(self.particles, desired_end_time, \ + start_time = start_time, N_output=1, \ + GCode=GCode, exportData=getOEData) + return self.particles, None def SimSingleEncounter(self, max_end_time, **kwargs): delta_time = kwargs.get("delta_time", 100 | units.yr) From ffaf5b6943cc9d02ec82a7e9272b17d11ee20cba Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 18 Aug 2020 16:16:11 -0400 Subject: [PATCH 073/130] Fixing cut encounter to not spawn multiple Keplers. --- cut_encounters.py | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/cut_encounters.py b/cut_encounters.py index 6709783..9850ff8 100644 --- a/cut_encounters.py +++ b/cut_encounters.py @@ -36,11 +36,20 @@ # Defining Functions # # ------------------------------------- # -def CutOrAdvance(enc_bodies, primary_sysID, converter=None): +def CutOrAdvance(enc_bodies, primary_sysID, converter=None, **kwargs): 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) + KeplerWorkerList = kwargs.get("kepler_workers", None) + # Initialize Kepler Workers if they Don't Exist + if KeplerWorkerList == None: + if converter == None: + converter = nbody_system.nbody_to_si(bodies.mass.sum(), 2 * np.max(bodies.radius.number) | bodies.radius.unit) + KeplerWorkerList = [] + for i in range(3): + KeplerWorkerList.append(Kepler(unit_converter = converter, redirection = 'none')) + KeplerWorkerList[i].initialize_code() + systems = stellar_systems.get_heirarchical_systems_from_set(bodies, \ + kepler_workers=KeplerWorkerList[:2], \ + 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 list(systems.keys()): print("...: Error: Previously run binary system has been found! Not running this system ...") @@ -68,8 +77,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): 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 = KeplerWorkerList[-1] 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() @@ -109,8 +117,10 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): 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() + # If not provided, stop Kepler and return the Systems as a Particle Set + if KeplerWorkerList == None: + for K in KeplerWorkerList: + K.stop() # Collect the Collective Particle Set to be Returned Back final_set = Particles() final_set.add_particles(sys_1) @@ -139,6 +149,12 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): log_file = open(os.getcwd()+"/cut_encounters.log","w") sys.stdout = log_file + # Create teh + KeplerWorkerList = [] + for i in range(3): + KeplerWorkerList.append(Kepler(unit_converter = converter, redirection = 'none')) + KeplerWorkerList[i].initialize_code() + # Read in Encounter Directory encounter_file = open(os.getcwd()+"/"+cluster_name+"_encounters.pkl", "rb") encounter_db = pickle.load(encounter_file) @@ -155,7 +171,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): del encounter_db[star_ID] if len(encounter_db[star_ID]) == 1: # Check to Ensure it is an Actual Multiples Initialization (AKA: 1 System) - temp = stellar_systems.get_heirarchical_systems_from_set(encounter_db[star_ID][0]) + temp = stellar_systems.get_heirarchical_systems_from_set(encounter_db[star_ID][0], kepler_workers=KeplerWorkerList[:2]) print(temp) if len(temp.keys()) <= 1: print(encounter_db[star_ID][0].id) @@ -193,7 +209,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): for star_ID in list(encounter_db.keys()): enc_id_to_cut = [] for enc_id, encounter in enumerate(encounter_db[star_ID]): - PeriastronCut = CutOrAdvance(encounter, star_ID) + PeriastronCut = CutOrAdvance(encounter, star_ID, kepler_workers=KeplerWorkerList) if PeriastronCut != None: encounter_db[star_ID][enc_id] = PeriastronCut elif PeriastronCut == None: @@ -215,3 +231,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None): sys.stdout = orig_stdout log_file.close() + + for K in KeplerWorkerList: + K.stop() + print("Finished cutting encounter database.") From 350d40170ba6aec32605865a5bb81a33694620a3 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 18 Aug 2020 16:21:33 -0400 Subject: [PATCH 074/130] Syntax fix. --- cut_encounters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cut_encounters.py b/cut_encounters.py index 9850ff8..b15c4c1 100644 --- a/cut_encounters.py +++ b/cut_encounters.py @@ -149,8 +149,9 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None, **kwargs): log_file = open(os.getcwd()+"/cut_encounters.log","w") sys.stdout = log_file - # Create teh + # Create the Kepler Workers KeplerWorkerList = [] + converter = nbody_system.nbody_to_si(1 | units.MSun, 100 |units.AU) for i in range(3): KeplerWorkerList.append(Kepler(unit_converter = converter, redirection = 'none')) KeplerWorkerList[i].initialize_code() From f643531dbd4097abd72c04e5fa7e7a50c42a60d3 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 18 Aug 2020 16:56:00 -0400 Subject: [PATCH 075/130] Fixed error in premature stopping of Kepler. --- cut_encounters.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cut_encounters.py b/cut_encounters.py index b15c4c1..5c93aaf 100644 --- a/cut_encounters.py +++ b/cut_encounters.py @@ -46,7 +46,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None, **kwargs): KeplerWorkerList = [] for i in range(3): KeplerWorkerList.append(Kepler(unit_converter = converter, redirection = 'none')) - KeplerWorkerList[i].initialize_code() + KeplerWorkerList[-1].initialize_code() systems = stellar_systems.get_heirarchical_systems_from_set(bodies, \ kepler_workers=KeplerWorkerList[:2], \ RelativePosition=False) @@ -84,7 +84,9 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None, **kwargs): 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() + if KeplerWorkerList == None: + for K in KeplerWorkerList: + K.stop() print("---------------------------------") return None # Move the Particles to be Relative to their Respective Center of Mass @@ -154,7 +156,7 @@ def CutOrAdvance(enc_bodies, primary_sysID, converter=None, **kwargs): converter = nbody_system.nbody_to_si(1 | units.MSun, 100 |units.AU) for i in range(3): KeplerWorkerList.append(Kepler(unit_converter = converter, redirection = 'none')) - KeplerWorkerList[i].initialize_code() + KeplerWorkerList[-1].initialize_code() # Read in Encounter Directory encounter_file = open(os.getcwd()+"/"+cluster_name+"_encounters.pkl", "rb") From 80d45bcca81ba013f9ee0e2b8e20039a9f8c4ca2 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 18 Aug 2020 17:56:47 -0400 Subject: [PATCH 076/130] Fixes to replace planetary system due to update to create.planet --- gen_RandEncounters.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gen_RandEncounters.py b/gen_RandEncounters.py index 597f67c..1b47c47 100644 --- a/gen_RandEncounters.py +++ b/gen_RandEncounters.py @@ -102,7 +102,9 @@ def replace_planetary_system(bodies, kepler_workers=None, base_planet_ID=50000, sys_with_planets.append(sys_key) # Add in a New Planetary System for sys_key in sys_with_planets: - planets = create.planetary_systems_v2(enc_systems[sys_key], 1, Jupiter=True, Earth=True, Neptune=True) + planets = create.planetary_systems_v2(enc_systems[sys_key], 1, Jupiter=True, \ + Earth=True, Neptune=True, \ + kepler_worker=kepler_workers[0]) enc_systems[sys_key].add_particles(planets) new_bodies = Particles() for sys_key in enc_systems: From 47bb87eee79d5c7c69627bfc0b184c7738739140 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 19 Aug 2020 13:40:34 -0400 Subject: [PATCH 077/130] Fixes to sim_encounter --- sim_encounters.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sim_encounters.py b/sim_encounters.py index aa496e6..19e8c64 100644 --- a/sim_encounters.py +++ b/sim_encounters.py @@ -83,7 +83,6 @@ def simulate_all_close_encounters(rootExecDir, **kwargs): max_number_of_rotations = kwargs.get("maxRotations", 100) max_runtime = kwargs.get("maxRunTime", 10**5) # Units Years delta_time = kwargs.get("dt", 10) # Units Years - GCodes = [initialize_GravCode(ph4), initialize_isOverCode()] # Strip off Extra '/' if added by user to bring inline with os.cwd() if rootExecDir.endswith("/"): rootExecDir = rootExecDir[:-1] @@ -103,7 +102,7 @@ def simulate_all_close_encounters(rootExecDir, **kwargs): for i in range(2): KepW.append(Kepler(unit_converter = converter, redirection = 'none')) KepW[-1].initialize_code() - NBodyW = [initialize_GravCode(ph4), initialize_isOverCode()] + NBodyW = [scattering.initialize_GravCode(ph4), scattering.initialize_isOverCode()] SecW = SecularMultiple() # Loop Over the Stars From a37175273115acdc6e7f946150af35d59cc6ef90 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 19 Aug 2020 14:22:49 -0400 Subject: [PATCH 078/130] Fix lack of SM import. --- sim_encounters.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sim_encounters.py b/sim_encounters.py index 19e8c64..fb78eca 100644 --- a/sim_encounters.py +++ b/sim_encounters.py @@ -37,6 +37,9 @@ from amuse.community.smalln.interface import SmallN from amuse.community.kepler.interface import Kepler from amuse.community.ph4.interface import ph4 +from amuse.community.secularmultiple.interface import SecularMultiple +from amuse.datamodel.trees import BinaryTreesOnAParticleSet +from amuse.ext.orbital_elements import new_binary_from_orbital_elements # Import the Tycho Packages from tycho import create, util, read, write, stellar_systems, enc_patching From 40f85602207d7045b6a44973f127183bdee916ab Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 19 Aug 2020 14:39:22 -0400 Subject: [PATCH 079/130] Sorting out issue. --- sim_encounters.py | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/sim_encounters.py b/sim_encounters.py index fb78eca..a62db7c 100644 --- a/sim_encounters.py +++ b/sim_encounters.py @@ -110,30 +110,26 @@ def simulate_all_close_encounters(rootExecDir, **kwargs): # Loop Over the Stars for star_ID in star_IDs: - try: - # Load the Close Encounter class for the Star - EncounterHandler = CloseEncounters(enc_dict[star_ID], KeplerWorkerList = KepW, \ - NBodyWorkerList = NBodyW, SecularWorker = SecW) - # Simulate Encounter - EncounterHandler.SimAllEncounters() - # Prepare Data for Pickling - file_name = output_MainDirectory+"/"+str(star_ID)+"_EncounterHandler.pk" - p_file = open(file_name, "wb") - # Remove Worker Lists from Class for Storage - EncounterHandler.kep = None - EncounterHandler.NBodyCodes = None - EncounterHandler.SecularCode = None - # Pickle EncounterHandler Class - # Note: This allows for ease-of-use when you want to revisit - # a specific star's simulation set in detail. - pickle.dump(EncounterHandler, p_file) - p_file.close() - # Note: Ensure the EncounterHandler class is deleted incase - # of a memory leak is possible in future updates. - del EncounterHandler - except: - print("Skipping StarID,", star_ID, "due to unforseen issues!") - pass + # Load the Close Encounter class for the Star + EncounterHandler = CloseEncounters(enc_dict[star_ID], KeplerWorkerList = KepW, \ + NBodyWorkerList = NBodyW, SecularWorker = SecW) + # Simulate Encounter + EncounterHandler.SimAllEncounters() + # Prepare Data for Pickling + file_name = output_MainDirectory+"/"+str(star_ID)+"_EncounterHandler.pk" + p_file = open(file_name, "wb") + # Remove Worker Lists from Class for Storage + EncounterHandler.kep = None + EncounterHandler.NBodyCodes = None + EncounterHandler.SecularCode = None + # Pickle EncounterHandler Class + # Note: This allows for ease-of-use when you want to revisit + # a specific star's simulation set in detail. + pickle.dump(EncounterHandler, p_file) + p_file.close() + # Note: Ensure the EncounterHandler class is deleted incase + # of a memory leak is possible in future updates. + del EncounterHandler # Stop all Workers for Worker in KepW+NBodyW+[SecW]: Worker.stop() From 6b8f2eb199ee931e2c1d547f7697a9becdfb646d Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 19 Aug 2020 14:40:17 -0400 Subject: [PATCH 080/130] Fixing module linking. --- sim_encounters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim_encounters.py b/sim_encounters.py index a62db7c..a67d012 100644 --- a/sim_encounters.py +++ b/sim_encounters.py @@ -111,7 +111,7 @@ def simulate_all_close_encounters(rootExecDir, **kwargs): # Loop Over the Stars for star_ID in star_IDs: # Load the Close Encounter class for the Star - EncounterHandler = CloseEncounters(enc_dict[star_ID], KeplerWorkerList = KepW, \ + EncounterHandler = scattering.CloseEncounters(enc_dict[star_ID], KeplerWorkerList = KepW, \ NBodyWorkerList = NBodyW, SecularWorker = SecW) # Simulate Encounter EncounterHandler.SimAllEncounters() From d754645ba5d0e2cf8215cf961f7521b69c2b5c83 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 19 Aug 2020 20:45:56 -0400 Subject: [PATCH 081/130] PatchedEncounter had a silly argument. --- src/tycho/scattering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 5f02f9b..55b322b 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -193,7 +193,7 @@ def SimAllEncounters(self): self.SecularCode.stop() return None - def PatchedEncounter(self, EndingState, NextEncounter, final_time): + def PatchedEncounter(self, EndingState, NextEncounter): ''' Call this function to Patch Encounter Endstates to the Next Encounter''' # Determine Time to Next Encounter current_time = max(EndingState.time) From 23c6e80effd38f5ae9698d7d54ab44cfa9c8596a Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 19 Aug 2020 20:49:55 -0400 Subject: [PATCH 082/130] Syntax fix. --- src/tycho/scattering.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 55b322b..d043f0f 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -204,9 +204,9 @@ def PatchedEncounter(self, EndingState, NextEncounter): # Seperate Next Encounter Systems to Locate the Primary System systems_at_next_encounter = stellar_systems.get_heirarchical_systems_from_set(NextEncounter) - sys_1 = systems[self.KeySystemID] + sys_1 = systems_at_next_encounter[self.KeySystemID] secondary_sysID = [key for key in list(systems_in_next_encounter.keys()) if key!=int(self.KeySystemID)][0] - sys_2 = systems[secondary_sysID] + sys_2 = systems_at_next_encounter[secondary_sysID] # Get Planet and Star Subsets for the Current and Next Encounter planets_at_current_encounter = util.get_planets(EndingState) From 552e6870ad9dc69b00759b2361ef1e0c43276607 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 19 Aug 2020 20:51:31 -0400 Subject: [PATCH 083/130] Syntax fix. --- src/tycho/scattering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index d043f0f..dfc53c7 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -205,7 +205,7 @@ def PatchedEncounter(self, EndingState, NextEncounter): # Seperate Next Encounter Systems to Locate the Primary System systems_at_next_encounter = stellar_systems.get_heirarchical_systems_from_set(NextEncounter) sys_1 = systems_at_next_encounter[self.KeySystemID] - secondary_sysID = [key for key in list(systems_in_next_encounter.keys()) if key!=int(self.KeySystemID)][0] + secondary_sysID = [key for key in list(systems_at_next_encounter.keys()) if key!=int(self.KeySystemID)][0] sys_2 = systems_at_next_encounter[secondary_sysID] # Get Planet and Star Subsets for the Current and Next Encounter From 22651c54a71dcd4b81d7edd337c3d0c56cf79985 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 19 Aug 2020 20:53:57 -0400 Subject: [PATCH 084/130] Syntax fix. --- src/tycho/scattering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index dfc53c7..c8cd09e 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -220,7 +220,7 @@ def PatchedEncounter(self, EndingState, NextEncounter): nbody_PlanetStarPair = \ new_binary_from_orbital_elements(hoststar_at_current_encounter.mass, planet.mass, planet.semimajor_axis, \ eccentricity = planet.eccentricity, inclination=planet.inclination, \ - longitude_of_the_ascending_node=planet.longitude_of_the_ascending_node, \ + longitude_of_the_ascending_node=planet.longitude_of_ascending_node, \ argument_of_periapsis=planet.argument_of_periapsis, G=units.constants.G, \ true_anomaly = 360*rp.uniform(0.0,1.0) | units.deg) # random point in the orbit planet.position = nbody_PlanetStarPair[1].position From 85448f19dcf0fc73cf352f8e525b87d8efb83021 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Wed, 19 Aug 2020 20:56:12 -0400 Subject: [PATCH 085/130] Syntax fix. --- src/tycho/scattering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index c8cd09e..bb3d1eb 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -221,7 +221,7 @@ def PatchedEncounter(self, EndingState, NextEncounter): new_binary_from_orbital_elements(hoststar_at_current_encounter.mass, planet.mass, planet.semimajor_axis, \ eccentricity = planet.eccentricity, inclination=planet.inclination, \ longitude_of_the_ascending_node=planet.longitude_of_ascending_node, \ - argument_of_periapsis=planet.argument_of_periapsis, G=units.constants.G, \ + argument_of_periapsis=planet.argument_of_pericenter, G=units.constants.G, \ true_anomaly = 360*rp.uniform(0.0,1.0) | units.deg) # random point in the orbit planet.position = nbody_PlanetStarPair[1].position planet.velocity = nbody_PlanetStarPair[1].velocity From b197e077a10578d3870f4e84117d81cc5f19e376 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 15:21:06 -0400 Subject: [PATCH 086/130] Fix to host star locating in EndState after SecularMultiples. --- src/tycho/scattering.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index bb3d1eb..aeff45d 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -209,8 +209,8 @@ def PatchedEncounter(self, EndingState, NextEncounter): sys_2 = systems_at_next_encounter[secondary_sysID] # Get Planet and Star Subsets for the Current and Next Encounter - planets_at_current_encounter = util.get_planets(EndingState) - hoststar_at_current_encounter = util.get_stars(EndingState) + planets_at_current_encounter = util.get_planets(EndingState).select(lambda x : x == False, ["is_binary"]) + hoststar_at_current_encounter = util.get_stars(EndingState).select(lambda x : x == False, ["is_binary"]) planets_at_next_encounter = util.get_planets(sys_1) hoststar_at_next_encounter = util.get_stars(sys_1) From cf82dfd92487e9275b4c43844ad68d23a94e64ce Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 15:26:47 -0400 Subject: [PATCH 087/130] Syntax error. --- src/tycho/scattering.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index aeff45d..510b8f2 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -238,8 +238,8 @@ def PatchedEncounter(self, EndingState, NextEncounter): for next_planet in planets_at_next_encounter: for current_planet in planets_at_current_encounter: if next_planet.id == current_planet.id: - next_planet.position = planets_at_current_encounter.position + hoststar_at_next_encounter.position - next_planet.velocity = planets_at_current_encounter.velocity + hoststar_at_next_encounter.velocity + next_planet.position = current_planet.position + hoststar_at_next_encounter.position + next_planet.velocity = current_planet.velocity + hoststar_at_next_encounter.velocity break # Recombine Seperated Systems to Feed into SimSingleEncounter From fad04ac6e069af8d77bd0e5ec96e16794c8b5b5d Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 15:31:34 -0400 Subject: [PATCH 088/130] Bug test. --- src/tycho/scattering.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 510b8f2..2aea62a 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -238,6 +238,7 @@ def PatchedEncounter(self, EndingState, NextEncounter): for next_planet in planets_at_next_encounter: for current_planet in planets_at_current_encounter: if next_planet.id == current_planet.id: + print(current_planet.position, hoststar_at_next_encounter.id, hoststar_at_next_encounter.position) next_planet.position = current_planet.position + hoststar_at_next_encounter.position next_planet.velocity = current_planet.velocity + hoststar_at_next_encounter.velocity break From a5239fc3325507fc9c8fcb949bbc824c24ac1fa1 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 15:33:04 -0400 Subject: [PATCH 089/130] Possible fix? --- src/tycho/scattering.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 2aea62a..4fd90f5 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -209,8 +209,8 @@ def PatchedEncounter(self, EndingState, NextEncounter): sys_2 = systems_at_next_encounter[secondary_sysID] # Get Planet and Star Subsets for the Current and Next Encounter - planets_at_current_encounter = util.get_planets(EndingState).select(lambda x : x == False, ["is_binary"]) - hoststar_at_current_encounter = util.get_stars(EndingState).select(lambda x : x == False, ["is_binary"]) + planets_at_current_encounter = util.get_planets(EndingState).select(lambda x : x == False, ["is_binary"])[0] + hoststar_at_current_encounter = util.get_stars(EndingState).select(lambda x : x == False, ["is_binary"])[0] planets_at_next_encounter = util.get_planets(sys_1) hoststar_at_next_encounter = util.get_stars(sys_1) From 38f9169b73fa452fb9139f45cbdb94098fb05aff Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 15:36:17 -0400 Subject: [PATCH 090/130] Possible fix. --- src/tycho/scattering.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 4fd90f5..8cac598 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -209,8 +209,9 @@ def PatchedEncounter(self, EndingState, NextEncounter): sys_2 = systems_at_next_encounter[secondary_sysID] # Get Planet and Star Subsets for the Current and Next Encounter - planets_at_current_encounter = util.get_planets(EndingState).select(lambda x : x == False, ["is_binary"])[0] - hoststar_at_current_encounter = util.get_stars(EndingState).select(lambda x : x == False, ["is_binary"])[0] + children_at_EndingState = EndingState.select(lambda x : x == False, ["is_binary"]) + planets_at_current_encounter = util.get_planets(EndingState) + hoststar_at_current_encounter = util.get_stars(EndingState)[0] planets_at_next_encounter = util.get_planets(sys_1) hoststar_at_next_encounter = util.get_stars(sys_1) From dd0f8d2bac8cdd61d4eedf7b1d678736b95e7c49 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 15:39:28 -0400 Subject: [PATCH 091/130] Possible fix. --- src/tycho/scattering.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 8cac598..4c153a7 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -210,8 +210,8 @@ def PatchedEncounter(self, EndingState, NextEncounter): # Get Planet and Star Subsets for the Current and Next Encounter children_at_EndingState = EndingState.select(lambda x : x == False, ["is_binary"]) - planets_at_current_encounter = util.get_planets(EndingState) - hoststar_at_current_encounter = util.get_stars(EndingState)[0] + planets_at_current_encounter = util.get_planets(children_at_EndingState) + hoststar_at_current_encounter = util.get_stars(children_at_EndingState)[0] planets_at_next_encounter = util.get_planets(sys_1) hoststar_at_next_encounter = util.get_stars(sys_1) From 6269b395bfb49c8f2528267bf918e8886c9f3872 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 15:41:35 -0400 Subject: [PATCH 092/130] Testing --- src/tycho/scattering.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 4c153a7..639b4f9 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -239,8 +239,8 @@ def PatchedEncounter(self, EndingState, NextEncounter): for next_planet in planets_at_next_encounter: for current_planet in planets_at_current_encounter: if next_planet.id == current_planet.id: - print(current_planet.position, hoststar_at_next_encounter.id, hoststar_at_next_encounter.position) - next_planet.position = current_planet.position + hoststar_at_next_encounter.position + print(current_planet.position, hoststar_at_next_encounter.id, hoststar_at_next_encounter.position[0]) + next_planet.position = current_planet.position + hoststar_at_next_encounter.position[0] next_planet.velocity = current_planet.velocity + hoststar_at_next_encounter.velocity break From 89887a42d15e861d0f5df76c10973bb1950c71c1 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 15:44:27 -0400 Subject: [PATCH 093/130] Testing ... --- src/tycho/scattering.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 639b4f9..de0e5e8 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -214,6 +214,7 @@ def PatchedEncounter(self, EndingState, NextEncounter): hoststar_at_current_encounter = util.get_stars(children_at_EndingState)[0] planets_at_next_encounter = util.get_planets(sys_1) hoststar_at_next_encounter = util.get_stars(sys_1) + print(sys_1) # Update Current Positions & Velocitys from Orbital Parameters!! # TO-DO: Does not handle Binary Star Systems From 9ce0f4cb1e23fa548d750dffe33080401a017943 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 15:49:57 -0400 Subject: [PATCH 094/130] Testing. --- src/tycho/scattering.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index de0e5e8..49734f3 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -213,8 +213,8 @@ def PatchedEncounter(self, EndingState, NextEncounter): planets_at_current_encounter = util.get_planets(children_at_EndingState) hoststar_at_current_encounter = util.get_stars(children_at_EndingState)[0] planets_at_next_encounter = util.get_planets(sys_1) - hoststar_at_next_encounter = util.get_stars(sys_1) - print(sys_1) + hoststar_at_next_encounter = util.get_stars(sys_1).select(lambda x : x == False, ["is_binary"]) + print(hoststar_at_next_encounter) # Update Current Positions & Velocitys from Orbital Parameters!! # TO-DO: Does not handle Binary Star Systems From ea062a8603a6e4873674b7d46881ae63c51d1391 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 15:51:32 -0400 Subject: [PATCH 095/130] Testing fix. --- src/tycho/scattering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 49734f3..d302207 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -213,7 +213,7 @@ def PatchedEncounter(self, EndingState, NextEncounter): planets_at_current_encounter = util.get_planets(children_at_EndingState) hoststar_at_current_encounter = util.get_stars(children_at_EndingState)[0] planets_at_next_encounter = util.get_planets(sys_1) - hoststar_at_next_encounter = util.get_stars(sys_1).select(lambda x : x == False, ["is_binary"]) + hoststar_at_next_encounter = util.get_stars(sys_1).select(lambda x : x == self.KeySystemID, ["id"]) print(hoststar_at_next_encounter) # Update Current Positions & Velocitys from Orbital Parameters!! From e9fb7011136ffb8097f9dbcd361030193c4e1030 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 15:53:40 -0400 Subject: [PATCH 096/130] This hopefully fixes it? --- src/tycho/scattering.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index d302207..1debb4b 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -213,7 +213,7 @@ def PatchedEncounter(self, EndingState, NextEncounter): planets_at_current_encounter = util.get_planets(children_at_EndingState) hoststar_at_current_encounter = util.get_stars(children_at_EndingState)[0] planets_at_next_encounter = util.get_planets(sys_1) - hoststar_at_next_encounter = util.get_stars(sys_1).select(lambda x : x == self.KeySystemID, ["id"]) + hoststar_at_next_encounter = util.get_stars(sys_1).select(lambda x : x == self.KeySystemID, ["id"])[0] print(hoststar_at_next_encounter) # Update Current Positions & Velocitys from Orbital Parameters!! @@ -240,8 +240,8 @@ def PatchedEncounter(self, EndingState, NextEncounter): for next_planet in planets_at_next_encounter: for current_planet in planets_at_current_encounter: if next_planet.id == current_planet.id: - print(current_planet.position, hoststar_at_next_encounter.id, hoststar_at_next_encounter.position[0]) - next_planet.position = current_planet.position + hoststar_at_next_encounter.position[0] + print(current_planet.position, hoststar_at_next_encounter.id, hoststar_at_next_encounter.position) + next_planet.position = current_planet.position + hoststar_at_next_encounter.position next_planet.velocity = current_planet.velocity + hoststar_at_next_encounter.velocity break From 605b3cd0a6c2be65f9609a3748d90e00ecd8c89a Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 15:59:08 -0400 Subject: [PATCH 097/130] Testing fix. --- src/tycho/scattering.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 1debb4b..8e91efc 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -219,6 +219,7 @@ def PatchedEncounter(self, EndingState, NextEncounter): # Update Current Positions & Velocitys from Orbital Parameters!! # TO-DO: Does not handle Binary Star Systems for planet in planets_at_current_encounter: + print(planet.id, planet.position) nbody_PlanetStarPair = \ new_binary_from_orbital_elements(hoststar_at_current_encounter.mass, planet.mass, planet.semimajor_axis, \ eccentricity = planet.eccentricity, inclination=planet.inclination, \ @@ -240,7 +241,7 @@ def PatchedEncounter(self, EndingState, NextEncounter): for next_planet in planets_at_next_encounter: for current_planet in planets_at_current_encounter: if next_planet.id == current_planet.id: - print(current_planet.position, hoststar_at_next_encounter.id, hoststar_at_next_encounter.position) + print(current_planet.id, current_planet.position) next_planet.position = current_planet.position + hoststar_at_next_encounter.position next_planet.velocity = current_planet.velocity + hoststar_at_next_encounter.velocity break From 55242c9afa9b26098c2870261e7b2bc5be55c1d9 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 16:04:19 -0400 Subject: [PATCH 098/130] Testing position carrying. --- src/tycho/scattering.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 8e91efc..3ef3fa9 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -228,6 +228,10 @@ def PatchedEncounter(self, EndingState, NextEncounter): true_anomaly = 360*rp.uniform(0.0,1.0) | units.deg) # random point in the orbit planet.position = nbody_PlanetStarPair[1].position planet.velocity = nbody_PlanetStarPair[1].velocity + print(planet.id, planet.position) + + for planet in planets_at_current_encounter: + print(planet.id, planet.position) # Get Relative Position + Velocity of Planets at the Current Encounter planets_at_current_encounter.position -= hoststar_at_current_encounter.position From 1bb1e1d0673e9c95282163f0fbd030560b4077e9 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 16:10:56 -0400 Subject: [PATCH 099/130] Testing --- src/tycho/scattering.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 3ef3fa9..58c40c5 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -214,12 +214,12 @@ def PatchedEncounter(self, EndingState, NextEncounter): hoststar_at_current_encounter = util.get_stars(children_at_EndingState)[0] planets_at_next_encounter = util.get_planets(sys_1) hoststar_at_next_encounter = util.get_stars(sys_1).select(lambda x : x == self.KeySystemID, ["id"])[0] - print(hoststar_at_next_encounter) + #print(hoststar_at_next_encounter) - # Update Current Positions & Velocitys from Orbital Parameters!! + # Update Current Positions & Velocitys to Relative Coordinates from Orbital Parameters!! # TO-DO: Does not handle Binary Star Systems for planet in planets_at_current_encounter: - print(planet.id, planet.position) + #print(planet.id, planet.position) nbody_PlanetStarPair = \ new_binary_from_orbital_elements(hoststar_at_current_encounter.mass, planet.mass, planet.semimajor_axis, \ eccentricity = planet.eccentricity, inclination=planet.inclination, \ @@ -228,15 +228,11 @@ def PatchedEncounter(self, EndingState, NextEncounter): true_anomaly = 360*rp.uniform(0.0,1.0) | units.deg) # random point in the orbit planet.position = nbody_PlanetStarPair[1].position planet.velocity = nbody_PlanetStarPair[1].velocity - print(planet.id, planet.position) + #print(planet.id, planet.position) for planet in planets_at_current_encounter: print(planet.id, planet.position) - # Get Relative Position + Velocity of Planets at the Current Encounter - planets_at_current_encounter.position -= hoststar_at_current_encounter.position - planets_at_current_encounter.velocity -= hoststar_at_current_encounter.velocity - # Release a Warning when Odd Planet Number Combinations Occur (Very Unlikely, More of a Safe Guard) if len(planets_at_current_encounter) != len(planets_at_next_encounter): print("!!!! Expected", len(planets_at_next_encounter), "planets but recieved only", len(planets_at_current_encounter)) @@ -245,9 +241,9 @@ def PatchedEncounter(self, EndingState, NextEncounter): for next_planet in planets_at_next_encounter: for current_planet in planets_at_current_encounter: if next_planet.id == current_planet.id: - print(current_planet.id, current_planet.position) next_planet.position = current_planet.position + hoststar_at_next_encounter.position next_planet.velocity = current_planet.velocity + hoststar_at_next_encounter.velocity + print(current_planet.id, current_planet.position) break # Recombine Seperated Systems to Feed into SimSingleEncounter From e3a5131ea392edddc3029817751285a42ca40af0 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 16:14:17 -0400 Subject: [PATCH 100/130] Testing position transferring. --- src/tycho/scattering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 58c40c5..60a2830 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -243,7 +243,7 @@ def PatchedEncounter(self, EndingState, NextEncounter): if next_planet.id == current_planet.id: next_planet.position = current_planet.position + hoststar_at_next_encounter.position next_planet.velocity = current_planet.velocity + hoststar_at_next_encounter.velocity - print(current_planet.id, current_planet.position) + print(next_planet.id, next_planet.position) break # Recombine Seperated Systems to Feed into SimSingleEncounter From 3d6761be1a1fe2df301724b506f593a9000352d6 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 16:18:00 -0400 Subject: [PATCH 101/130] Fix applied! --- src/tycho/scattering.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 60a2830..0870560 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -243,11 +243,10 @@ def PatchedEncounter(self, EndingState, NextEncounter): if next_planet.id == current_planet.id: next_planet.position = current_planet.position + hoststar_at_next_encounter.position next_planet.velocity = current_planet.velocity + hoststar_at_next_encounter.velocity - print(next_planet.id, next_planet.position) break # Recombine Seperated Systems to Feed into SimSingleEncounter - UpdatedNextEncounter = Partciles() + UpdatedNextEncounter = Particles() UpdatedNextEncounter.add_particles(sys1) UpdatedNextEncounter.add_particles(sys2) From 0cb792488cf5a26e6cb6e470be53bc5baec68a6c Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 16:20:25 -0400 Subject: [PATCH 102/130] Test to ensure sys1 gets update. --- src/tycho/scattering.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 0870560..e212964 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -245,8 +245,14 @@ def PatchedEncounter(self, EndingState, NextEncounter): next_planet.velocity = current_planet.velocity + hoststar_at_next_encounter.velocity break + for planet in planets_at_next_encounter: + print(planet.id, planet.position) + for particle in sys_1: + print(particle.id, particle.position) + + # Recombine Seperated Systems to Feed into SimSingleEncounter - UpdatedNextEncounter = Particles() + UpdatedNextEncounter = Partciles() UpdatedNextEncounter.add_particles(sys1) UpdatedNextEncounter.add_particles(sys2) From 0a36a789d06e8e23544c11673d1d917a82811cf9 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 16:22:04 -0400 Subject: [PATCH 103/130] Finalized fix. --- src/tycho/scattering.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index e212964..97636ac 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -245,14 +245,14 @@ def PatchedEncounter(self, EndingState, NextEncounter): next_planet.velocity = current_planet.velocity + hoststar_at_next_encounter.velocity break - for planet in planets_at_next_encounter: - print(planet.id, planet.position) - for particle in sys_1: - print(particle.id, particle.position) + #for planet in planets_at_next_encounter: + # print(planet.id, planet.position) + #for particle in sys_1: + # print(particle.id, particle.position) # Recombine Seperated Systems to Feed into SimSingleEncounter - UpdatedNextEncounter = Partciles() + UpdatedNextEncounter = Particles() UpdatedNextEncounter.add_particles(sys1) UpdatedNextEncounter.add_particles(sys2) From 56fab0daddbe768f44d279924c079c55e4308cc3 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 16:23:41 -0400 Subject: [PATCH 104/130] Small syntax error. --- src/tycho/scattering.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 97636ac..dc7cfb3 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -253,8 +253,8 @@ def PatchedEncounter(self, EndingState, NextEncounter): # Recombine Seperated Systems to Feed into SimSingleEncounter UpdatedNextEncounter = Particles() - UpdatedNextEncounter.add_particles(sys1) - UpdatedNextEncounter.add_particles(sys2) + UpdatedNextEncounter.add_particles(sys_1) + UpdatedNextEncounter.add_particles(sys_2) # Return the Updated and Patched Encounter as a Partcile Set for the N-Body Simulation return UpdatedNextEncounter From 888343ac32270a967590787db7a9524ad27de316 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 16:33:30 -0400 Subject: [PATCH 105/130] Testing fix for maximum end_times. --- src/tycho/scattering.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index dc7cfb3..2adec50 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -126,9 +126,9 @@ def SimAllEncounters(self): # Simulate the Encounter till the Encounter is Over via N-Body Integrator # -OR- the time to the Next Encounter is Reached - if len(self.StartTimes[RotationKey]) == 1: + if len(self.StartTimes[RotationKey]) == 1 or i+1 == len(self.StartTimes[RotationKey]): current_max_endtime = self.max_end_time - else: + elif i+1 >= len(self.ICs[RotationKey]): current_max_endtime = self.StartTimes[RotationKey][i+1] EndingState = Encounter_Inst.SimSingleEncounter(current_max_endtime, \ start_time = self.StartTimes[RotationKey][i], \ From 07eb2fa207afca9d0899b2eebb955fd134232604 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 16:36:04 -0400 Subject: [PATCH 106/130] Potential Fix to endtimes. --- src/tycho/scattering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 2adec50..ef18aa1 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -128,7 +128,7 @@ def SimAllEncounters(self): # -OR- the time to the Next Encounter is Reached if len(self.StartTimes[RotationKey]) == 1 or i+1 == len(self.StartTimes[RotationKey]): current_max_endtime = self.max_end_time - elif i+1 >= len(self.ICs[RotationKey]): + else: current_max_endtime = self.StartTimes[RotationKey][i+1] EndingState = Encounter_Inst.SimSingleEncounter(current_max_endtime, \ start_time = self.StartTimes[RotationKey][i], \ From d0f2125a1581690375001f46c20f8afeb0c9e02d Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 16:53:36 -0400 Subject: [PATCH 107/130] Weird hierarchy creation ... --- src/tycho/scattering.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index ef18aa1..b5f4e68 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -116,6 +116,7 @@ def SimAllEncounters(self): # Begin Looping over Rotation Keys ... for RotationKey in self.ICs.keys(): for i in range(len(self.ICs[RotationKey])): + print("!!!!!!", self.KeySystemID, RotationKey) # Identify the Current Encounter in the List for This Rotation CurrentEncounter = self.ICs[RotationKey][i] From eb1785f45490c56f37e1e0811ce69dbd57df1c55 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Thu, 20 Aug 2020 16:55:20 -0400 Subject: [PATCH 108/130] Debug print for weird encounter. --- src/tycho/scattering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index b5f4e68..8e9e593 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -116,7 +116,7 @@ def SimAllEncounters(self): # Begin Looping over Rotation Keys ... for RotationKey in self.ICs.keys(): for i in range(len(self.ICs[RotationKey])): - print("!!!!!!", self.KeySystemID, RotationKey) + print("!!!!!!", self.KeySystemID, RotationKey, i) # Identify the Current Encounter in the List for This Rotation CurrentEncounter = self.ICs[RotationKey][i] From 2bfad46c30aad9843029c2f044808a8132044131 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Fri, 21 Aug 2020 16:02:56 -0400 Subject: [PATCH 109/130] Testing oddity. --- src/tycho/scattering.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 8e9e593..c42727f 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -140,9 +140,9 @@ def SimAllEncounters(self): print("The Encounter was over after:", (EndingStateTime- self.StartTimes[RotationKey][i]).value_in(units.Myr)) - #print(EndingState) - #print('----------') - #print(Encounter_Inst.particles) + print(EndingState.id, EndingState.x) + print('----------') + print(Encounter_Inst.particles.id, Encounter_Inst.particles.x) # Strip off Anything Not Associated with the Key System systems_in_current_encounter = stellar_systems.get_heirarchical_systems_from_set(EndingState, kepler_workers=self.kep) From d6d3d64e0b5461c808d2114312c18df650a2efc8 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Fri, 21 Aug 2020 16:58:29 -0400 Subject: [PATCH 110/130] Fix to deal with non-mutual nearest neighbours. --- src/tycho/stellar_systems.py | 57 ++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/src/tycho/stellar_systems.py b/src/tycho/stellar_systems.py index 629f74d..4cbba03 100644 --- a/src/tycho/stellar_systems.py +++ b/src/tycho/stellar_systems.py @@ -334,7 +334,9 @@ def get_planetary_systems_from_set(bodies, converter=None, RelativePosition=Fals # Note: The below function is nearly identical to the above function. However, # it is to be used for determining "clumps" of systems for the CutOrAdvance -# function primarily. ~ Joe G. 4/1/20 +# function primarily. ~ Joe G. 4/1/20 +# Note: This was updated to deal with stars who are bound but not mutually their +# respected closest neighbours. ~ Joe G. 8/21/20 def get_heirarchical_systems_from_set(bodies, kepler_workers=None, converter=None, RelativePosition=False): # Initialize Kepler if kepler_workers == None: @@ -358,34 +360,45 @@ def get_heirarchical_systems_from_set(bodies, kepler_workers=None, converter=Non 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 - #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) - noStellarHeirarchy = False + noStellarHierarchy = False + # If there is only a single star, there is obviously no stellar heirarchy if len(stars) == 1: - noStellarHeirarchy = True + noStellarHierarchy = True + # 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): - if other_star.id in binary_ids: - continue + # 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) + star.vx - other_star.vx, star.vy - other_star.vy, star.vz - other_star.vz) a_s, e_s = kep_s.get_elements() - #print a_s, e_s - #r_apo = kep_s.get_apastron() - #HillR = util.calc_HillRadius(a_s, e_s, other_star.mass, star.mass) - #print r_apo, HillR - if e_s >= 1.0 and other_star.id != closest_neighbours[index]: - noStellarHeirarchy = True + 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: - noStellarHeirarchy = 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) + # 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) for planet in planets: total_mass = star.mass + planet.mass @@ -396,18 +409,18 @@ def get_heirarchical_systems_from_set(bodies, kepler_workers=None, converter=Non 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 noStellarHeirarchy: + if noStellarHierarchy: # 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. + # Note: This is empty for now, maybe consider doing it by the + # heaviest bound stellar object as the primary. pass else: continue From e1537ff1cb3cdb7df4b3d5afca0fc685f6a51465 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 24 Aug 2020 11:52:59 -0400 Subject: [PATCH 111/130] Fixes to Encounter Patching to deal with compact objects. --- src/tycho/scattering.py | 13 ++++++++--- src/tycho/stellar_systems.py | 44 ++++++++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index c42727f..893d129 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -206,8 +206,14 @@ def PatchedEncounter(self, EndingState, NextEncounter): # Seperate Next Encounter Systems to Locate the Primary System systems_at_next_encounter = stellar_systems.get_heirarchical_systems_from_set(NextEncounter) sys_1 = systems_at_next_encounter[self.KeySystemID] - secondary_sysID = [key for key in list(systems_at_next_encounter.keys()) if key!=int(self.KeySystemID)][0] - sys_2 = systems_at_next_encounter[secondary_sysID] + # Note: This was changed to handle encounters of which result in one + # bound object of multiple subsystems. ~ Joe G. | 8/24/20 + BoundObjOnly = False + if len(systems_at_next_encounter.keys()) == 1: + BoundObjOnly = True + else: + secondary_sysID = [key for key in list(systems_at_next_encounter.keys()) if key!=int(self.KeySystemID)][0] + sys_2 = systems_at_next_encounter[secondary_sysID] # Get Planet and Star Subsets for the Current and Next Encounter children_at_EndingState = EndingState.select(lambda x : x == False, ["is_binary"]) @@ -255,7 +261,8 @@ def PatchedEncounter(self, EndingState, NextEncounter): # Recombine Seperated Systems to Feed into SimSingleEncounter UpdatedNextEncounter = Particles() UpdatedNextEncounter.add_particles(sys_1) - UpdatedNextEncounter.add_particles(sys_2) + if not BoundObjOnly: + UpdatedNextEncounter.add_particles(sys_2) # Return the Updated and Patched Encounter as a Partcile Set for the N-Body Simulation return UpdatedNextEncounter diff --git a/src/tycho/stellar_systems.py b/src/tycho/stellar_systems.py index 4cbba03..b77a8d4 100644 --- a/src/tycho/stellar_systems.py +++ b/src/tycho/stellar_systems.py @@ -368,14 +368,15 @@ def get_heirarchical_systems_from_set(bodies, kepler_workers=None, converter=Non current_system = systems.setdefault(system_id, Particles()) current_system.add_particle(star) noStellarHierarchy = False - # If there is only a single star, there is obviously no stellar heirarchy + # If there is only one stars, there is obviously no stellar heirarchy in + # the encounter that is occuring. if len(stars) == 1: noStellarHierarchy = True # 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): + for other_star in (stars-star) and not noStellarHierarchy: # 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) @@ -406,6 +407,9 @@ def get_heirarchical_systems_from_set(bodies, kepler_workers=None, converter=Non 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 ... @@ -413,15 +417,41 @@ def get_heirarchical_systems_from_set(bodies, kepler_workers=None, converter=Non # 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.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: This is empty for now, maybe consider doing it by the - # heaviest bound stellar object as the primary. - pass + # 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 util.get_stars(current_system): + 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 = ap2 + 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: continue if kepler_workers == None: From 7aeca2066e99ef5e12c8502326c59008fddb2502 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 24 Aug 2020 11:57:17 -0400 Subject: [PATCH 112/130] Fix to syntax. --- src/tycho/stellar_systems.py | 54 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/tycho/stellar_systems.py b/src/tycho/stellar_systems.py index b77a8d4..a3e4b0c 100644 --- a/src/tycho/stellar_systems.py +++ b/src/tycho/stellar_systems.py @@ -372,34 +372,34 @@ def get_heirarchical_systems_from_set(bodies, kepler_workers=None, converter=Non # the encounter that is occuring. if len(stars) == 1: noStellarHierarchy = True - # 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) and not noStellarHierarchy: - # 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) + 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: - 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) + # 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) for planet in planets: total_mass = star.mass + planet.mass From 8ed537576245c7758b52114228e815577f9e3af0 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 24 Aug 2020 11:58:34 -0400 Subject: [PATCH 113/130] Syntax fix. --- src/tycho/stellar_systems.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/stellar_systems.py b/src/tycho/stellar_systems.py index a3e4b0c..17b6840 100644 --- a/src/tycho/stellar_systems.py +++ b/src/tycho/stellar_systems.py @@ -376,7 +376,7 @@ def get_heirarchical_systems_from_set(bodies, kepler_workers=None, converter=Non # 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) + 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) From d0fb1a78a65136c85fab40945a4a43d666b9796e Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 24 Aug 2020 14:26:12 -0400 Subject: [PATCH 114/130] Changed list_of_sqd to loop in ascending order of distances per body --- src/tycho/enc_patching.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 0befc4b..884d7d9 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -83,7 +83,7 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): print('First Pass') pass list_of_sqd = bodies.distances_squared(body).number - for nonzero_sqd in list_of_sqd[np.nonzero(list_of_sqd)]: + for nonzero_sqd in sorted(list_of_sqd[np.nonzero(list_of_sqd)]): print("Finding closet Neighbor for ID:", body.id) # Assign the Closest Partner closest_partner = bodies[[i for i, j in enumerate(list_of_sqd) if j == nonzero_sqd]] From 3417c053a4e3359875527ea2eaaa32d4b86718f0 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 24 Aug 2020 15:27:07 -0400 Subject: [PATCH 115/130] Fixed incorrect CoM setting when multiple systems exist in a binary. --- src/tycho/enc_patching.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 884d7d9..39bd155 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -153,8 +153,8 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): root_particle.period = 2.0*np.pi/np.sqrt(constants.G*(bigbro.mass))*semimajor_axis**(3./2.) root_particle.child1 = bigbro root_particle.child2 = lilsis - root_particle.position = (hierarchical_set.select(lambda x : x == False, ["is_binary"])).center_of_mass() - root_particle.velocity = (hierarchical_set.select(lambda x : x == False, ["is_binary"])).center_of_mass_velocity() + root_particle.position = (hierarchical_set.select(lambda x : x == bigbro.id or x == lilsis.id, ["id"])).center_of_mass() + root_particle.velocity = (hierarchical_set.select(lambda x : x == bigbro.id or x == lilsis.id, ["id"])).center_of_mass_velocity() root_particle.id = root_particle.child1.id+root_particle.child2.id hierarchical_set.add_particle(root_particle) break From a0270a636df2c6bd4aca541ae060ed2b1d4b321d Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 24 Aug 2020 16:19:28 -0400 Subject: [PATCH 116/130] Changes to print statements --- src/tycho/enc_patching.py | 14 +++++++------- src/tycho/scattering.py | 23 +++++++++++++---------- src/tycho/stellar_systems.py | 4 ++-- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 39bd155..8297891 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -56,7 +56,7 @@ def get_physical_radius(particle): elif particle.mass <= 0.1 | units.MJupiter: return 4 | units.REarth else: - print(particle.id, particle.mass.value_in(units.MJupiter), "MJupiter") + #print(particle.id, particle.mass.value_in(units.MJupiter), "MJupiter") return 1 | units.RJupiter else: return util.get_stellar_radius(particle) @@ -70,9 +70,9 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): ''' hierarchical_set = Particles() for body in bodies: - print(body.id) + #print(body.id) body.radius = get_physical_radius(body) - print(body.radius) + #print(body.radius) # Calculate Distances to All Bodies for Each Body for index, body in enumerate(bodies): try: @@ -95,14 +95,14 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): if node != None: print('Heirarchical Set when finding nodes:',[x.id for x in hierarchical_set if x.is_binary==True]) closest_partner = node - print("Closest Neighbor is:", closest_partner.id) + print("New Closest Neighbor is:", closest_partner.id) # Calculate the Orbital Elements k_set = Particles() k_set.add_particle(body.copy()) k_set.add_particle(closest_partner.copy()) k_set_sorted = k_set.sorted_by_attribute('mass')[::-1] # Ensures Heaviest is First - print(k_set.id) + #print(k_set.id) (mass1, mass2, semimajor_axis, eccentricity, \ true_anomaly, inclination, long_asc_node, arg_per) \ = get_orbital_elements_from_binary(k_set_sorted, G=constants.G) @@ -121,7 +121,7 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): temp.radius = bigbro.radius temp.position = bigbro.position temp.velocity = bigbro.velocity - print(bigbro.velocity) + #print(bigbro.velocity) hierarchical_set.add_particle(temp) # Child1 is at -2 if lilsis not in hierarchical_set: temp = Particle() @@ -133,7 +133,7 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): temp.radius = lilsis.radius temp.position = lilsis.position temp.velocity = lilsis.velocity - print(lilsis.velocity) + #print(lilsis.velocity) hierarchical_set.add_particle(temp) # Child2 is at -1 # Reset bigbro and lilsis to the copies in the set i1 = np.where(hierarchical_set.id==bigbro.id)[0][0] diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 893d129..af14cfb 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -120,7 +120,7 @@ def SimAllEncounters(self): # Identify the Current Encounter in the List for This Rotation CurrentEncounter = self.ICs[RotationKey][i] - print(CurrentEncounter[0].position) + #print(CurrentEncounter[0].position) # Create the Encounter Instance with the Current Encounter Encounter_Inst = self.SingleEncounter(CurrentEncounter) @@ -136,22 +136,23 @@ def SimAllEncounters(self): GCodes = self.NBodyCodes) EndingStateTime = np.max(np.unique(EndingState.time)) - print(Encounter_Inst.particles[0].position) + #print(Encounter_Inst.particles[0].position) print("The Encounter was over after:", (EndingStateTime- self.StartTimes[RotationKey][i]).value_in(units.Myr)) - print(EndingState.id, EndingState.x) + #print(EndingState.id, EndingState.x) print('----------') - print(Encounter_Inst.particles.id, Encounter_Inst.particles.x) + #print(Encounter_Inst.particles.id, Encounter_Inst.particles.x) # Strip off Anything Not Associated with the Key System systems_in_current_encounter = stellar_systems.get_heirarchical_systems_from_set(EndingState, kepler_workers=self.kep) # Reassign the EndingState to include the Primary System ONLY EndingState = systems_in_current_encounter[self.KeySystemID] + print("Before Secular:", EndingState.id, EndingState.x) #print(EndingState[0].position) - print(len(self.ICs[RotationKey])-1) + #print(len(self.ICs[RotationKey])-1) # If Encounter Patching is Desired -AND- it isn't the last Encounter if i + 1 < len(self.ICs[RotationKey]) and self.doEncounterPatching: @@ -163,7 +164,7 @@ def SimAllEncounters(self): FinalState, data = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], \ start_time = EndingStateTime, \ GCode = self.SecularCode, getOEData=self.getOEData) - + print("After Secular:", FinalState.id) # Begin Patching of the End State to the Next Encounter self.ICs[RotationKey][i+1] = self.PatchedEncounter(FinalState, NextEncounter) else: @@ -174,7 +175,7 @@ def SimAllEncounters(self): FinalState, data = Encounter_Inst.SimSecularSystem(self.desired_endtime, \ start_time = EndingStateTime, \ GCode = self.SecularCode, getOEData=self.getOEData) - #print(FinalState[0].position) + print("After Secular:", FinalState.id) # Append the FinalState of Each Encounter to its Dictionary self.FinalStates[RotationKey].append(FinalState) @@ -218,7 +219,7 @@ def PatchedEncounter(self, EndingState, NextEncounter): # Get Planet and Star Subsets for the Current and Next Encounter children_at_EndingState = EndingState.select(lambda x : x == False, ["is_binary"]) planets_at_current_encounter = util.get_planets(children_at_EndingState) - hoststar_at_current_encounter = util.get_stars(children_at_EndingState)[0] + hoststar_at_current_encounter = util.get_stars(children_at_EndingState).select(lambda x : x == self.KeySystemID, ["id"])[0] planets_at_next_encounter = util.get_planets(sys_1) hoststar_at_next_encounter = util.get_stars(sys_1).select(lambda x : x == self.KeySystemID, ["id"])[0] #print(hoststar_at_next_encounter) @@ -260,9 +261,11 @@ def PatchedEncounter(self, EndingState, NextEncounter): # Recombine Seperated Systems to Feed into SimSingleEncounter UpdatedNextEncounter = Particles() + print("IDs in System 1", sys_1.id) UpdatedNextEncounter.add_particles(sys_1) if not BoundObjOnly: UpdatedNextEncounter.add_particles(sys_2) + print("IDs in System 2", sys_2.id) # Return the Updated and Patched Encounter as a Partcile Set for the N-Body Simulation return UpdatedNextEncounter @@ -361,8 +364,8 @@ def SimSingleEncounter(self, max_end_time, **kwargs): over = util.check_isOver(gravity.particles, over_grav) print("Is it Over?", over) if over: - print(gravity.particles[0].position) - print(GravitatingBodies[0].position) + #print(gravity.particles[0].position) + #print(GravitatingBodies[0].position) current_time += 100 | units.yr # Get to a Final State After Several Planet Orbits gravity.evolve_model(current_time) diff --git a/src/tycho/stellar_systems.py b/src/tycho/stellar_systems.py index 17b6840..05ac3d6 100644 --- a/src/tycho/stellar_systems.py +++ b/src/tycho/stellar_systems.py @@ -381,14 +381,14 @@ def get_heirarchical_systems_from_set(bodies, kepler_workers=None, converter=Non 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) + print("Checking Stars' Eccentricity:", 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) + #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!") From 328ceafc6531885c57949aba087a1c312bb13aba Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 24 Aug 2020 17:12:09 -0400 Subject: [PATCH 117/130] Adding more debug. --- src/tycho/scattering.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index af14cfb..d881cac 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -221,6 +221,7 @@ def PatchedEncounter(self, EndingState, NextEncounter): planets_at_current_encounter = util.get_planets(children_at_EndingState) hoststar_at_current_encounter = util.get_stars(children_at_EndingState).select(lambda x : x == self.KeySystemID, ["id"])[0] planets_at_next_encounter = util.get_planets(sys_1) + print("Planets at Next Encount:", planets_at_next_encounter.id) hoststar_at_next_encounter = util.get_stars(sys_1).select(lambda x : x == self.KeySystemID, ["id"])[0] #print(hoststar_at_next_encounter) @@ -251,7 +252,9 @@ def PatchedEncounter(self, EndingState, NextEncounter): if next_planet.id == current_planet.id: next_planet.position = current_planet.position + hoststar_at_next_encounter.position next_planet.velocity = current_planet.velocity + hoststar_at_next_encounter.velocity + #planets_updated.append(next_planet.id) break + #extra_planets = planets_at_next_encounter - #for planet in planets_at_next_encounter: # print(planet.id, planet.position) From 026cd0e7bb6c2e9e66df7dc6d7e3bc81a18300f2 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 24 Aug 2020 17:19:51 -0400 Subject: [PATCH 118/130] Upating stellar_systems --- src/tycho/stellar_systems.py | 53 ++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/src/tycho/stellar_systems.py b/src/tycho/stellar_systems.py index 05ac3d6..411dd5d 100644 --- a/src/tycho/stellar_systems.py +++ b/src/tycho/stellar_systems.py @@ -381,14 +381,14 @@ def get_heirarchical_systems_from_set(bodies, kepler_workers=None, converter=Non 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("Checking Stars' Eccentricity:", star.id, other_star.id, e_s) + 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) + 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!") @@ -400,8 +400,19 @@ def get_heirarchical_systems_from_set(bodies, kepler_workers=None, converter=Non 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 @@ -428,7 +439,7 @@ def get_heirarchical_systems_from_set(bodies, kepler_workers=None, converter=Non # 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 util.get_stars(current_system): + 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 @@ -439,7 +450,7 @@ def get_heirarchical_systems_from_set(bodies, kepler_workers=None, converter=Non # 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 = ap2 + a_p = a_p2 e_p = e_p2 P_p = kep_p.get_period() Ta_p, Ma_p = kep_p.get_angles() @@ -452,8 +463,38 @@ def get_heirarchical_systems_from_set(bodies, kepler_workers=None, converter=Non 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: - continue + print("!!! Alert: Planet is not bound nor is it bound to any other star.") if kepler_workers == None: kep_p.stop() kep_s.stop() From e86de7de4a6a343cdc8c3b31f06ee0f8ca4729f7 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 24 Aug 2020 17:49:48 -0400 Subject: [PATCH 119/130] Update to deal with better sorting for get_heirarchical_systems. --- src/tycho/enc_patching.py | 12 ++++++++++-- src/tycho/scattering.py | 3 ++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 8297891..e930e95 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -63,7 +63,7 @@ def get_physical_radius(particle): -def get_full_hierarchical_structure(bodies, RelativePosition=False): +def get_full_hierarchical_structure(bodies, RelativePosition=False, KeySystemID=None): ''' This function creates a tree-based particle set for use in hierarchical particle integrators (like SecularMultiple). @@ -73,7 +73,15 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False): #print(body.id) body.radius = get_physical_radius(body) #print(body.radius) - # Calculate Distances to All Bodies for Each Body + # If Desired, Center the Structure on a Specific Key Body's ID + if KeySystemID!= None: + # We do this by sorting the next loop to advance in ascending order + # according to distance from the designated KeyBody. + KeyBody = bodies[bodies.id == KeySystemID] + for i, dist in enumerate(bodies.distances_squared(KeyBody)): + bodies[i].distsqd_KeyCenter = dist[0].number | dist[0].unit + bodies = bodies.sorted_by_attribute("distsqd_KeyCenter") + for index, body in enumerate(bodies): try: if body.id in hierarchical_set.id: diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index d881cac..42fa00b 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -205,7 +205,8 @@ def PatchedEncounter(self, EndingState, NextEncounter): enc_patching.map_node_oe_to_lilsis(EndingState) # Seperate Next Encounter Systems to Locate the Primary System - systems_at_next_encounter = stellar_systems.get_heirarchical_systems_from_set(NextEncounter) + systems_at_next_encounter = stellar_systems.get_heirarchical_systems_from_set(NextEncounter, \ + KeySystemID=self.KeySystemID) sys_1 = systems_at_next_encounter[self.KeySystemID] # Note: This was changed to handle encounters of which result in one # bound object of multiple subsystems. ~ Joe G. | 8/24/20 From 8d36e532277848a023d1816aa4cb0b193a0c2fc6 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 24 Aug 2020 18:16:52 -0400 Subject: [PATCH 120/130] Quick update to run_secularMultiples. --- src/tycho/enc_patching.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index e930e95..d6cb02a 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -383,6 +383,7 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ py_particles.time = time if exportData: plot_times_Myr.append(time.value_in(units.Myr)) + nodes = py_particles.select(lambda x : x == True, ["is_binary"]) for i, node in enumerate(nodes): plot_a_AU[node.child2.id].append(node.semimajor_axis.value_in(units.AU)) plot_e[node.child2.id].append(node.eccentricity) From 9ea65a0100ab4e83d8f7e848bbf813e68d95b48e Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 24 Aug 2020 18:49:20 -0400 Subject: [PATCH 121/130] Changes to include centering hierarchy. --- src/tycho/enc_patching.py | 8 ++++---- src/tycho/scattering.py | 16 ++++++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index d6cb02a..c122dec 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -169,7 +169,7 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False, KeySystemID= continue return hierarchical_set.copy() -def check_for_stellar_collision(hierarchical_set): +def check_for_stellar_collision(hierarchical_set, KeySystemID=None): ''' This function checks for a planet entering into the Roche Limit of its parent star. This is meant to be used as a check for Secular @@ -196,7 +196,7 @@ def check_for_stellar_collision(hierarchical_set): # Update Position and Velocity Vectors temp = update_posvel_from_oe(temp) # Hierarchy is Rebuilt and Returned - return get_full_hierarchical_structure(temp) + return get_full_hierarchical_structure(temp, KeySystemID=KeySystemID) return None def update_posvel_from_oe(particle_set): @@ -286,7 +286,7 @@ def update_oe_for_PlanetarySystem(PS, hierarchical_set): def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ N_output=100, debug_mode=False, genT4System=False, \ - exportData=True, useAMD=True, GCode = None): + exportData=True, useAMD=True, GCode = None, KeySystemID=None): '''Does what it says on the tin.''' try: hierarchical_test = [x for x in particle_set if x.is_binary == True] @@ -294,7 +294,7 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ py_particles = particle_set except: print("The supplied set is NOT a tree set! Building tree ...") - py_particles = get_full_hierarchical_structure(particle_set) + py_particles = get_full_hierarchical_structure(particle_set, KeySystemID=KeySystemID) hierarchical_test = [x for x in py_particles if x.is_binary == True] print("Tree has been built with", len(hierarchical_test), "node particles.") nodes = py_particles.select(lambda x : x == True, ["is_binary"]) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 42fa00b..66b44ab 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -163,7 +163,8 @@ def SimAllEncounters(self): Encounter_Inst = self.SingleEncounter(EndingState) FinalState, data = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], \ start_time = EndingStateTime, \ - GCode = self.SecularCode, getOEData=self.getOEData) + GCode = self.SecularCode, getOEData=self.getOEData, \ + KeySystemID = self.KeySystemID) print("After Secular:", FinalState.id) # Begin Patching of the End State to the Next Encounter self.ICs[RotationKey][i+1] = self.PatchedEncounter(FinalState, NextEncounter) @@ -174,7 +175,8 @@ def SimAllEncounters(self): Encounter_Inst = self.SingleEncounter(EndingState) FinalState, data = Encounter_Inst.SimSecularSystem(self.desired_endtime, \ start_time = EndingStateTime, \ - GCode = self.SecularCode, getOEData=self.getOEData) + GCode = self.SecularCode, getOEData=self.getOEData \ + KeySystemID = self.KeySystemID) print("After Secular:", FinalState.id) # Append the FinalState of Each Encounter to its Dictionary @@ -205,8 +207,7 @@ def PatchedEncounter(self, EndingState, NextEncounter): enc_patching.map_node_oe_to_lilsis(EndingState) # Seperate Next Encounter Systems to Locate the Primary System - systems_at_next_encounter = stellar_systems.get_heirarchical_systems_from_set(NextEncounter, \ - KeySystemID=self.KeySystemID) + systems_at_next_encounter = stellar_systems.get_heirarchical_systems_from_set(NextEncounter) sys_1 = systems_at_next_encounter[self.KeySystemID] # Note: This was changed to handle encounters of which result in one # bound object of multiple subsystems. ~ Joe G. | 8/24/20 @@ -281,16 +282,19 @@ def __init__(self, EncounterBodies): def SimSecularSystem(self, desired_end_time, **kwargs): start_time = kwargs.get("start_time", 0 | units.Myr) getOEData = kwargs.get("getOEData", False) + KeySystemID = kwargs.get("KeySystemID", None) GCode = kwargs.get("GCode", None) if getOEData: self.particles, data = enc_patching.run_secularmultiple(self.particles, desired_end_time, \ start_time = start_time, N_output=1, \ - GCode=GCode, exportData=getOEData) + GCode=GCode, exportData=getOEData, \ + KeySystemID=KeySystemID) return self.particles, data else: self.particles = enc_patching.run_secularmultiple(self.particles, desired_end_time, \ start_time = start_time, N_output=1, \ - GCode=GCode, exportData=getOEData) + GCode=GCode, exportData=getOEData, \ + KeySystemID=KeySystemID) return self.particles, None def SimSingleEncounter(self, max_end_time, **kwargs): From 10fa6f476cf994b9b313d163e0eff5143718780a Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Mon, 24 Aug 2020 18:50:12 -0400 Subject: [PATCH 122/130] Syntax fix. --- src/tycho/scattering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 66b44ab..38e9759 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -175,7 +175,7 @@ def SimAllEncounters(self): Encounter_Inst = self.SingleEncounter(EndingState) FinalState, data = Encounter_Inst.SimSecularSystem(self.desired_endtime, \ start_time = EndingStateTime, \ - GCode = self.SecularCode, getOEData=self.getOEData \ + GCode = self.SecularCode, getOEData=self.getOEData, \ KeySystemID = self.KeySystemID) print("After Secular:", FinalState.id) From d0e09a73612c200eb9491b72267043a525899772 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 25 Aug 2020 11:31:36 -0400 Subject: [PATCH 123/130] Fixed issues stemming from stellar inspiral collision resets. --- src/tycho/enc_patching.py | 12 ++++++++++-- src/tycho/scattering.py | 22 +++++++++------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index c122dec..224bfc0 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -299,6 +299,7 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ print("Tree has been built with", len(hierarchical_test), "node particles.") nodes = py_particles.select(lambda x : x == True, ["is_binary"]) Num_nodes = len(nodes) + stellarCollisionOccured = False if GCode == None: code = SecularMultiple() @@ -417,11 +418,13 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ py_particles = Particles() py_particles.add_particles(temp) code.particles.add_particles(py_particles) + py_particles.time = time #code.commit_particles() channel_from_particles_to_code = py_particles.new_channel_to(code.particles) channel_from_code_to_particles = code.particles.new_channel_to(py_particles) channel_from_particles_to_code.copy() nodes = py_particles.select(lambda x : x == True, ["is_binary"]) + stellarCollisionOccured = True if useAMD: PS = initialize_PlanetarySystem_from_HierarchicalSet(py_particles) #channel_from_code_to_particles.copy_attributes(['semimajor_axis', 'eccentricity', \ @@ -446,6 +449,11 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ data = plot_times_Myr,plot_a_AU, plot_e, plot_peri_AU, plot_stellar_inc_deg, plot_AMDBeta else: data = plot_times_Myr,plot_a_AU, plot_e, plot_peri_AU, plot_stellar_inc_deg - return py_particles, data else: - return py_particles + data = None + # Set the Output Code to be the New Code if a Stellar Collision Occured. + if stellarCollisionOccured: + newcode = code + else: + newcode = None + return py_particles, data, newcode diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 38e9759..7bde290 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -161,10 +161,12 @@ def SimAllEncounters(self): # Simulate System till the Next Encounter's Start Time Encounter_Inst = self.SingleEncounter(EndingState) - FinalState, data = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], \ + FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], \ start_time = EndingStateTime, \ GCode = self.SecularCode, getOEData=self.getOEData, \ KeySystemID = self.KeySystemID) + if newcode != None: + self.SecularCode = newcode print("After Secular:", FinalState.id) # Begin Patching of the End State to the Next Encounter self.ICs[RotationKey][i+1] = self.PatchedEncounter(FinalState, NextEncounter) @@ -173,10 +175,12 @@ def SimAllEncounters(self): #print(CurrentEncounter[0].time.value_in(units.Myr)) #print(EndingState[0].time.value_in(units.Myr)) Encounter_Inst = self.SingleEncounter(EndingState) - FinalState, data = Encounter_Inst.SimSecularSystem(self.desired_endtime, \ + FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.desired_endtime, \ start_time = EndingStateTime, \ GCode = self.SecularCode, getOEData=self.getOEData, \ KeySystemID = self.KeySystemID) + if newcode != None: + self.SecularCode = newcode print("After Secular:", FinalState.id) # Append the FinalState of Each Encounter to its Dictionary @@ -254,9 +258,7 @@ def PatchedEncounter(self, EndingState, NextEncounter): if next_planet.id == current_planet.id: next_planet.position = current_planet.position + hoststar_at_next_encounter.position next_planet.velocity = current_planet.velocity + hoststar_at_next_encounter.velocity - #planets_updated.append(next_planet.id) break - #extra_planets = planets_at_next_encounter - #for planet in planets_at_next_encounter: # print(planet.id, planet.position) @@ -284,18 +286,12 @@ def SimSecularSystem(self, desired_end_time, **kwargs): getOEData = kwargs.get("getOEData", False) KeySystemID = kwargs.get("KeySystemID", None) GCode = kwargs.get("GCode", None) - if getOEData: - self.particles, data = enc_patching.run_secularmultiple(self.particles, desired_end_time, \ - start_time = start_time, N_output=1, \ - GCode=GCode, exportData=getOEData, \ - KeySystemID=KeySystemID) - return self.particles, data - else: - self.particles = enc_patching.run_secularmultiple(self.particles, desired_end_time, \ + + self.particles, data, newcode = enc_patching.run_secularmultiple(self.particles, desired_end_time, \ start_time = start_time, N_output=1, \ GCode=GCode, exportData=getOEData, \ KeySystemID=KeySystemID) - return self.particles, None + return self.particles, data, newcode def SimSingleEncounter(self, max_end_time, **kwargs): delta_time = kwargs.get("delta_time", 100 | units.yr) From 474b907f3159baa7bdb0256e5dd03210f8c0e6d6 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 25 Aug 2020 11:42:15 -0400 Subject: [PATCH 124/130] Temporary fix; keystars not having planets at certain instances. --- src/tycho/scattering.py | 146 +++++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 71 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 7bde290..f5708c7 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -116,77 +116,81 @@ def SimAllEncounters(self): # Begin Looping over Rotation Keys ... for RotationKey in self.ICs.keys(): for i in range(len(self.ICs[RotationKey])): - print("!!!!!!", self.KeySystemID, RotationKey, i) - - # Identify the Current Encounter in the List for This Rotation - CurrentEncounter = self.ICs[RotationKey][i] - #print(CurrentEncounter[0].position) - - # Create the Encounter Instance with the Current Encounter - Encounter_Inst = self.SingleEncounter(CurrentEncounter) - - # Simulate the Encounter till the Encounter is Over via N-Body Integrator - # -OR- the time to the Next Encounter is Reached - if len(self.StartTimes[RotationKey]) == 1 or i+1 == len(self.StartTimes[RotationKey]): - current_max_endtime = self.max_end_time - else: - current_max_endtime = self.StartTimes[RotationKey][i+1] - EndingState = Encounter_Inst.SimSingleEncounter(current_max_endtime, \ - start_time = self.StartTimes[RotationKey][i], \ - GCodes = self.NBodyCodes) - EndingStateTime = np.max(np.unique(EndingState.time)) - - #print(Encounter_Inst.particles[0].position) - print("The Encounter was over after:", (EndingStateTime- self.StartTimes[RotationKey][i]).value_in(units.Myr)) - - - #print(EndingState.id, EndingState.x) - print('----------') - #print(Encounter_Inst.particles.id, Encounter_Inst.particles.x) - - # Strip off Anything Not Associated with the Key System - systems_in_current_encounter = stellar_systems.get_heirarchical_systems_from_set(EndingState, kepler_workers=self.kep) - - # Reassign the EndingState to include the Primary System ONLY - EndingState = systems_in_current_encounter[self.KeySystemID] - print("Before Secular:", EndingState.id, EndingState.x) - #print(EndingState[0].position) - - #print(len(self.ICs[RotationKey])-1) - # If Encounter Patching is Desired -AND- it isn't the last Encounter - if i + 1 < len(self.ICs[RotationKey]) and self.doEncounterPatching: - - # Identify the Next Encounter in the List - NextEncounter = self.ICs[RotationKey][i+1] - - # Simulate System till the Next Encounter's Start Time - Encounter_Inst = self.SingleEncounter(EndingState) - FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], \ - start_time = EndingStateTime, \ - GCode = self.SecularCode, getOEData=self.getOEData, \ - KeySystemID = self.KeySystemID) - if newcode != None: - self.SecularCode = newcode - print("After Secular:", FinalState.id) - # Begin Patching of the End State to the Next Encounter - self.ICs[RotationKey][i+1] = self.PatchedEncounter(FinalState, NextEncounter) - else: - # Simulate System till Desired Global Endtime - #print(CurrentEncounter[0].time.value_in(units.Myr)) - #print(EndingState[0].time.value_in(units.Myr)) - Encounter_Inst = self.SingleEncounter(EndingState) - FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.desired_endtime, \ - start_time = EndingStateTime, \ - GCode = self.SecularCode, getOEData=self.getOEData, \ - KeySystemID = self.KeySystemID) - if newcode != None: - self.SecularCode = newcode - print("After Secular:", FinalState.id) - - # Append the FinalState of Each Encounter to its Dictionary - self.FinalStates[RotationKey].append(FinalState) - if self.getOEData and data != None: - self.OEData[RotationKey].append(data) + try: + print("!!!!!!", self.KeySystemID, RotationKey, i) + + # Identify the Current Encounter in the List for This Rotation + CurrentEncounter = self.ICs[RotationKey][i] + #print(CurrentEncounter[0].position) + + # Create the Encounter Instance with the Current Encounter + Encounter_Inst = self.SingleEncounter(CurrentEncounter) + + # Simulate the Encounter till the Encounter is Over via N-Body Integrator + # -OR- the time to the Next Encounter is Reached + if len(self.StartTimes[RotationKey]) == 1 or i+1 == len(self.StartTimes[RotationKey]): + current_max_endtime = self.max_end_time + else: + current_max_endtime = self.StartTimes[RotationKey][i+1] + EndingState = Encounter_Inst.SimSingleEncounter(current_max_endtime, \ + start_time = self.StartTimes[RotationKey][i], \ + GCodes = self.NBodyCodes) + EndingStateTime = np.max(np.unique(EndingState.time)) + + #print(Encounter_Inst.particles[0].position) + print("The Encounter was over after:", (EndingStateTime- self.StartTimes[RotationKey][i]).value_in(units.Myr)) + + + #print(EndingState.id, EndingState.x) + print('----------') + #print(Encounter_Inst.particles.id, Encounter_Inst.particles.x) + + # Strip off Anything Not Associated with the Key System + systems_in_current_encounter = stellar_systems.get_heirarchical_systems_from_set(EndingState, kepler_workers=self.kep) + + # Reassign the EndingState to include the Primary System ONLY + EndingState = systems_in_current_encounter[self.KeySystemID] + print("Before Secular:", EndingState.id, EndingState.x) + #print(EndingState[0].position) + + #print(len(self.ICs[RotationKey])-1) + # If Encounter Patching is Desired -AND- it isn't the last Encounter + if i + 1 < len(self.ICs[RotationKey]) and self.doEncounterPatching: + + # Identify the Next Encounter in the List + NextEncounter = self.ICs[RotationKey][i+1] + + # Simulate System till the Next Encounter's Start Time + Encounter_Inst = self.SingleEncounter(EndingState) + FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], \ + start_time = EndingStateTime, \ + GCode = self.SecularCode, getOEData=self.getOEData, \ + KeySystemID = self.KeySystemID) + if newcode != None: + self.SecularCode = newcode + print("After Secular:", FinalState.id) + # Begin Patching of the End State to the Next Encounter + self.ICs[RotationKey][i+1] = self.PatchedEncounter(FinalState, NextEncounter) + else: + # Simulate System till Desired Global Endtime + #print(CurrentEncounter[0].time.value_in(units.Myr)) + #print(EndingState[0].time.value_in(units.Myr)) + Encounter_Inst = self.SingleEncounter(EndingState) + FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.desired_endtime, \ + start_time = EndingStateTime, \ + GCode = self.SecularCode, getOEData=self.getOEData, \ + KeySystemID = self.KeySystemID) + if newcode != None: + self.SecularCode = newcode + print("After Secular:", FinalState.id) + + # Append the FinalState of Each Encounter to its Dictionary + self.FinalStates[RotationKey].append(FinalState) + if self.getOEData and data != None: + self.OEData[RotationKey].append(data) + except: + print("!!!! Alert: Skipping", RotationKey,"-", i, "for Star", self.KeySystemID, "due to unforseen issues!") + print("!!!! The Particle Set's IDs are as follows:", self.ICs[RotationKey][i].id) # Stop the NBody Codes if not Provided if self.kep == None: From 766b3d7a3d585906911b38608ec5b2d18c3f08a8 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 25 Aug 2020 17:56:41 -0400 Subject: [PATCH 125/130] Fix need of SSE worker to be passed on some systems. --- sim_encounters.py | 4 +++- src/tycho/enc_patching.py | 19 +++++++++++-------- src/tycho/scattering.py | 14 ++++++++++---- src/tycho/stellar_systems.py | 1 + src/tycho/util.py | 13 ++++++++++--- 5 files changed, 35 insertions(+), 16 deletions(-) diff --git a/sim_encounters.py b/sim_encounters.py index a67d012..f421b56 100644 --- a/sim_encounters.py +++ b/sim_encounters.py @@ -38,6 +38,7 @@ from amuse.community.kepler.interface import Kepler from amuse.community.ph4.interface import ph4 from amuse.community.secularmultiple.interface import SecularMultiple +from amuse.community.sse.interface import SSE from amuse.datamodel.trees import BinaryTreesOnAParticleSet from amuse.ext.orbital_elements import new_binary_from_orbital_elements @@ -107,12 +108,13 @@ def simulate_all_close_encounters(rootExecDir, **kwargs): KepW[-1].initialize_code() NBodyW = [scattering.initialize_GravCode(ph4), scattering.initialize_isOverCode()] SecW = SecularMultiple() + SEVW = SSE() # Loop Over the Stars for star_ID in star_IDs: # Load the Close Encounter class for the Star EncounterHandler = scattering.CloseEncounters(enc_dict[star_ID], KeplerWorkerList = KepW, \ - NBodyWorkerList = NBodyW, SecularWorker = SecW) + NBodyWorkerList = NBodyW, SecularWorker = SecW, SEVWorer = SEVW) # Simulate Encounter EncounterHandler.SimAllEncounters() # Prepare Data for Pickling diff --git a/src/tycho/enc_patching.py b/src/tycho/enc_patching.py index 224bfc0..e03b8cb 100644 --- a/src/tycho/enc_patching.py +++ b/src/tycho/enc_patching.py @@ -12,6 +12,7 @@ from amuse.community.secularmultiple.interface import SecularMultiple from amuse.datamodel.trees import BinaryTreesOnAParticleSet from amuse.ext.orbital_elements import get_orbital_elements_from_binary +from amuse.community.sse.interface import SSE set_printing_strategy("custom", preferred_units = [units.MSun, units.AU, units.day, units.deg], precision = 6, prefix = "", separator = "[", suffix = "]") @@ -40,7 +41,7 @@ def get_root_of_leaf(particle_set, chosen_id): -def get_physical_radius(particle): +def get_physical_radius(particle, SEVCode=None): ''' This is a very basic function which pulls an estimate for the radius of a planet for use in Secular integrators. @@ -59,11 +60,12 @@ def get_physical_radius(particle): #print(particle.id, particle.mass.value_in(units.MJupiter), "MJupiter") return 1 | units.RJupiter else: - return util.get_stellar_radius(particle) + return util.get_stellar_radius(particle, SEVCode) -def get_full_hierarchical_structure(bodies, RelativePosition=False, KeySystemID=None): +def get_full_hierarchical_structure(bodies, RelativePosition=False, \ + KeySystemID=None, SEVCode = None): ''' This function creates a tree-based particle set for use in hierarchical particle integrators (like SecularMultiple). @@ -71,7 +73,7 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False, KeySystemID= hierarchical_set = Particles() for body in bodies: #print(body.id) - body.radius = get_physical_radius(body) + body.radius = get_physical_radius(body, SEVCode=SEVCode) #print(body.radius) # If Desired, Center the Structure on a Specific Key Body's ID if KeySystemID!= None: @@ -169,7 +171,7 @@ def get_full_hierarchical_structure(bodies, RelativePosition=False, KeySystemID= continue return hierarchical_set.copy() -def check_for_stellar_collision(hierarchical_set, KeySystemID=None): +def check_for_stellar_collision(hierarchical_set, KeySystemID=None, SEVCode=None): ''' This function checks for a planet entering into the Roche Limit of its parent star. This is meant to be used as a check for Secular @@ -196,7 +198,7 @@ def check_for_stellar_collision(hierarchical_set, KeySystemID=None): # Update Position and Velocity Vectors temp = update_posvel_from_oe(temp) # Hierarchy is Rebuilt and Returned - return get_full_hierarchical_structure(temp, KeySystemID=KeySystemID) + return get_full_hierarchical_structure(temp, KeySystemID=KeySystemID, SEVCode=SEVCode) return None def update_posvel_from_oe(particle_set): @@ -286,7 +288,8 @@ def update_oe_for_PlanetarySystem(PS, hierarchical_set): def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ N_output=100, debug_mode=False, genT4System=False, \ - exportData=True, useAMD=True, GCode = None, KeySystemID=None): + exportData=True, useAMD=True, GCode = None, \ + KeySystemID=None, SEVCode=None): '''Does what it says on the tin.''' try: hierarchical_test = [x for x in particle_set if x.is_binary == True] @@ -294,7 +297,7 @@ def run_secularmultiple(particle_set, end_time, start_time=(0 |units.Myr), \ py_particles = particle_set except: print("The supplied set is NOT a tree set! Building tree ...") - py_particles = get_full_hierarchical_structure(particle_set, KeySystemID=KeySystemID) + py_particles = get_full_hierarchical_structure(particle_set, KeySystemID=KeySystemID, SEVCode=SEVCode) hierarchical_test = [x for x in py_particles if x.is_binary == True] print("Tree has been built with", len(hierarchical_test), "node particles.") nodes = py_particles.select(lambda x : x == True, ["is_binary"]) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index f5708c7..61fa22e 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -22,6 +22,7 @@ from amuse.community.smalln.interface import SmallN from amuse.community.kepler.interface import Kepler from amuse.community.ph4.interface import ph4 +from amuse.community.sse.interface import SSE set_printing_strategy("custom", preferred_units = [units.MSun, units.AU, units.day, units.deg], precision = 6, prefix = "", separator = "[", suffix = "]") @@ -73,7 +74,8 @@ def initialize_isOverCode(**kwargs): return isOverCode class CloseEncounters(): - def __init__(self, Star_EncounterHistory, KeplerWorkerList = None, NBodyWorkerList = None, SecularWorker = None): + def __init__(self, Star_EncounterHistory, KeplerWorkerList = None, \ + NBodyWorkerList = None, SecularWorker = None, SEVWorker = None): '''EncounterHistory should be a List of the Format {RotationKey: [Encounter0_FilePath, ...]}''' # Find the Main System's Host Star's ID and Assign it to 'KeySystemID' self.doEncounterPatching = True @@ -85,6 +87,7 @@ def __init__(self, Star_EncounterHistory, KeplerWorkerList = None, NBodyWorkerLi self.kep = KeplerWorkerList self.NBodyCodes = NBodyWorkerList self.SecularCode = SecularWorker + self.SEVCode = SEVWorker self.getOEData = True self.OEData = defaultdict(list) # Create a List of StartingTimes and Encounter Initial Conditions (ICs) for all Orientations @@ -112,6 +115,8 @@ def SimAllEncounters(self): # Start up SecularCode if Needed if self.SecularCode == None: self.SecularCode = SecularMultiple() + if self.SEVCode == None: + self.SEVCode = SSE() # Begin Looping over Rotation Keys ... for RotationKey in self.ICs.keys(): @@ -165,7 +170,7 @@ def SimAllEncounters(self): FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], \ start_time = EndingStateTime, \ GCode = self.SecularCode, getOEData=self.getOEData, \ - KeySystemID = self.KeySystemID) + KeySystemID = self.KeySystemID, SCode=self.SEVCode) if newcode != None: self.SecularCode = newcode print("After Secular:", FinalState.id) @@ -179,7 +184,7 @@ def SimAllEncounters(self): FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.desired_endtime, \ start_time = EndingStateTime, \ GCode = self.SecularCode, getOEData=self.getOEData, \ - KeySystemID = self.KeySystemID) + KeySystemID = self.KeySystemID, SCode=self.SEVCode) if newcode != None: self.SecularCode = newcode print("After Secular:", FinalState.id) @@ -290,11 +295,12 @@ def SimSecularSystem(self, desired_end_time, **kwargs): getOEData = kwargs.get("getOEData", False) KeySystemID = kwargs.get("KeySystemID", None) GCode = kwargs.get("GCode", None) + SEVCode = kwargs.get("SCode", None) self.particles, data, newcode = enc_patching.run_secularmultiple(self.particles, desired_end_time, \ start_time = start_time, N_output=1, \ GCode=GCode, exportData=getOEData, \ - KeySystemID=KeySystemID) + KeySystemID=KeySystemID, SEVCode=SEVCode) return self.particles, data, newcode def SimSingleEncounter(self, max_end_time, **kwargs): diff --git a/src/tycho/stellar_systems.py b/src/tycho/stellar_systems.py index 411dd5d..e56304d 100644 --- a/src/tycho/stellar_systems.py +++ b/src/tycho/stellar_systems.py @@ -33,6 +33,7 @@ from tycho import util from amuse.community.kepler.interface import Kepler +from amuse.community.sse.interface import SSE # ------------------------------------- # # Defining Functions # diff --git a/src/tycho/util.py b/src/tycho/util.py index 41e509e..08260a7 100644 --- a/src/tycho/util.py +++ b/src/tycho/util.py @@ -31,6 +31,7 @@ from amuse.lab import * from amuse.ic.brokenimf import MultiplePartIMF +from amuse.community.sse.interface import SSE # ------------------------------------- # # Defining Functions # @@ -262,8 +263,11 @@ def ensure_approaching_binary(primary, secondary, kepler_worker=None): return primary, secondary -def get_stellar_radius(star): - sev_code = SSE() +def get_stellar_radius(star, SEVCode = None): + if SEVCode == None: + sev_code = SSE() + else: + sev_code = SEVCode temp_star = Particle() temp_star.mass = star.mass temp_star.age = star.time @@ -272,7 +276,10 @@ def get_stellar_radius(star): sev_code.evolve_model(star.time) radius = sev_code.particles[0].radius print(sev_code.particles[0].radius) - sev_code.stop() + if SEVCode == None: + sev_code.stop() + else: + sev_code.particles = Particles() return radius def resolve_supernova(supernova_detection, bodies, time): From 167a5a66997ff5200f8bb2f505ac9431a25d8d64 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 25 Aug 2020 17:59:00 -0400 Subject: [PATCH 126/130] Syntax error --- sim_encounters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim_encounters.py b/sim_encounters.py index f421b56..b1a6967 100644 --- a/sim_encounters.py +++ b/sim_encounters.py @@ -114,7 +114,7 @@ def simulate_all_close_encounters(rootExecDir, **kwargs): for star_ID in star_IDs: # Load the Close Encounter class for the Star EncounterHandler = scattering.CloseEncounters(enc_dict[star_ID], KeplerWorkerList = KepW, \ - NBodyWorkerList = NBodyW, SecularWorker = SecW, SEVWorer = SEVW) + NBodyWorkerList = NBodyW, SecularWorker = SecW, SEVWorker = SEVW) # Simulate Encounter EncounterHandler.SimAllEncounters() # Prepare Data for Pickling From 8a4133ff1269a6f3901e25ee1876da15cfb84648 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 25 Aug 2020 18:36:17 -0400 Subject: [PATCH 127/130] Fixed to remove SEVCode from pickled file. --- sim_encounters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sim_encounters.py b/sim_encounters.py index b1a6967..f85a3ed 100644 --- a/sim_encounters.py +++ b/sim_encounters.py @@ -124,6 +124,7 @@ def simulate_all_close_encounters(rootExecDir, **kwargs): EncounterHandler.kep = None EncounterHandler.NBodyCodes = None EncounterHandler.SecularCode = None + EncounterHandler.SEVCode = None # Pickle EncounterHandler Class # Note: This allows for ease-of-use when you want to revisit # a specific star's simulation set in detail. @@ -133,7 +134,7 @@ def simulate_all_close_encounters(rootExecDir, **kwargs): # of a memory leak is possible in future updates. del EncounterHandler # Stop all Workers - for Worker in KepW+NBodyW+[SecW]: + for Worker in KepW+NBodyW+[SecW]+SEVW: Worker.stop() From 7baf75911273f51e5c6f20c0f31ea1a650d0fd97 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 25 Aug 2020 18:59:31 -0400 Subject: [PATCH 128/130] Checking an oddity. --- src/tycho/scattering.py | 150 ++++++++++++++++++++-------------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 61fa22e..99a797d 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -121,81 +121,81 @@ def SimAllEncounters(self): # Begin Looping over Rotation Keys ... for RotationKey in self.ICs.keys(): for i in range(len(self.ICs[RotationKey])): - try: - print("!!!!!!", self.KeySystemID, RotationKey, i) - - # Identify the Current Encounter in the List for This Rotation - CurrentEncounter = self.ICs[RotationKey][i] - #print(CurrentEncounter[0].position) - - # Create the Encounter Instance with the Current Encounter - Encounter_Inst = self.SingleEncounter(CurrentEncounter) - - # Simulate the Encounter till the Encounter is Over via N-Body Integrator - # -OR- the time to the Next Encounter is Reached - if len(self.StartTimes[RotationKey]) == 1 or i+1 == len(self.StartTimes[RotationKey]): - current_max_endtime = self.max_end_time - else: - current_max_endtime = self.StartTimes[RotationKey][i+1] - EndingState = Encounter_Inst.SimSingleEncounter(current_max_endtime, \ - start_time = self.StartTimes[RotationKey][i], \ - GCodes = self.NBodyCodes) - EndingStateTime = np.max(np.unique(EndingState.time)) - - #print(Encounter_Inst.particles[0].position) - print("The Encounter was over after:", (EndingStateTime- self.StartTimes[RotationKey][i]).value_in(units.Myr)) - - - #print(EndingState.id, EndingState.x) - print('----------') - #print(Encounter_Inst.particles.id, Encounter_Inst.particles.x) - - # Strip off Anything Not Associated with the Key System - systems_in_current_encounter = stellar_systems.get_heirarchical_systems_from_set(EndingState, kepler_workers=self.kep) - - # Reassign the EndingState to include the Primary System ONLY - EndingState = systems_in_current_encounter[self.KeySystemID] - print("Before Secular:", EndingState.id, EndingState.x) - #print(EndingState[0].position) - - #print(len(self.ICs[RotationKey])-1) - # If Encounter Patching is Desired -AND- it isn't the last Encounter - if i + 1 < len(self.ICs[RotationKey]) and self.doEncounterPatching: - - # Identify the Next Encounter in the List - NextEncounter = self.ICs[RotationKey][i+1] - - # Simulate System till the Next Encounter's Start Time - Encounter_Inst = self.SingleEncounter(EndingState) - FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], \ - start_time = EndingStateTime, \ - GCode = self.SecularCode, getOEData=self.getOEData, \ - KeySystemID = self.KeySystemID, SCode=self.SEVCode) - if newcode != None: - self.SecularCode = newcode - print("After Secular:", FinalState.id) - # Begin Patching of the End State to the Next Encounter - self.ICs[RotationKey][i+1] = self.PatchedEncounter(FinalState, NextEncounter) - else: - # Simulate System till Desired Global Endtime - #print(CurrentEncounter[0].time.value_in(units.Myr)) - #print(EndingState[0].time.value_in(units.Myr)) - Encounter_Inst = self.SingleEncounter(EndingState) - FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.desired_endtime, \ - start_time = EndingStateTime, \ - GCode = self.SecularCode, getOEData=self.getOEData, \ - KeySystemID = self.KeySystemID, SCode=self.SEVCode) - if newcode != None: - self.SecularCode = newcode - print("After Secular:", FinalState.id) - - # Append the FinalState of Each Encounter to its Dictionary - self.FinalStates[RotationKey].append(FinalState) - if self.getOEData and data != None: - self.OEData[RotationKey].append(data) - except: - print("!!!! Alert: Skipping", RotationKey,"-", i, "for Star", self.KeySystemID, "due to unforseen issues!") - print("!!!! The Particle Set's IDs are as follows:", self.ICs[RotationKey][i].id) + #try: + print("!!!!!!", self.KeySystemID, RotationKey, i) + + # Identify the Current Encounter in the List for This Rotation + CurrentEncounter = self.ICs[RotationKey][i] + #print(CurrentEncounter[0].position) + + # Create the Encounter Instance with the Current Encounter + Encounter_Inst = self.SingleEncounter(CurrentEncounter) + + # Simulate the Encounter till the Encounter is Over via N-Body Integrator + # -OR- the time to the Next Encounter is Reached + if len(self.StartTimes[RotationKey]) == 1 or i+1 == len(self.StartTimes[RotationKey]): + current_max_endtime = self.max_end_time + else: + current_max_endtime = self.StartTimes[RotationKey][i+1] + EndingState = Encounter_Inst.SimSingleEncounter(current_max_endtime, \ + start_time = self.StartTimes[RotationKey][i], \ + GCodes = self.NBodyCodes) + EndingStateTime = np.max(np.unique(EndingState.time)) + + #print(Encounter_Inst.particles[0].position) + print("The Encounter was over after:", (EndingStateTime- self.StartTimes[RotationKey][i]).value_in(units.Myr)) + + + #print(EndingState.id, EndingState.x) + print('----------') + #print(Encounter_Inst.particles.id, Encounter_Inst.particles.x) + + # Strip off Anything Not Associated with the Key System + systems_in_current_encounter = stellar_systems.get_heirarchical_systems_from_set(EndingState, kepler_workers=self.kep) + + # Reassign the EndingState to include the Primary System ONLY + EndingState = systems_in_current_encounter[self.KeySystemID] + print("Before Secular:", EndingState.id, EndingState.x) + #print(EndingState[0].position) + + #print(len(self.ICs[RotationKey])-1) + # If Encounter Patching is Desired -AND- it isn't the last Encounter + if i + 1 < len(self.ICs[RotationKey]) and self.doEncounterPatching: + + # Identify the Next Encounter in the List + NextEncounter = self.ICs[RotationKey][i+1] + + # Simulate System till the Next Encounter's Start Time + Encounter_Inst = self.SingleEncounter(EndingState) + FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], \ + start_time = EndingStateTime, \ + GCode = self.SecularCode, getOEData=self.getOEData, \ + KeySystemID = self.KeySystemID, SCode=self.SEVCode) + if newcode != None: + self.SecularCode = newcode + print("After Secular:", FinalState.id) + # Begin Patching of the End State to the Next Encounter + self.ICs[RotationKey][i+1] = self.PatchedEncounter(FinalState, NextEncounter) + else: + # Simulate System till Desired Global Endtime + #print(CurrentEncounter[0].time.value_in(units.Myr)) + #print(EndingState[0].time.value_in(units.Myr)) + Encounter_Inst = self.SingleEncounter(EndingState) + FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.desired_endtime, \ + start_time = EndingStateTime, \ + GCode = self.SecularCode, getOEData=self.getOEData, \ + KeySystemID = self.KeySystemID, SCode=self.SEVCode) + if newcode != None: + self.SecularCode = newcode + print("After Secular:", FinalState.id) + + # Append the FinalState of Each Encounter to its Dictionary + self.FinalStates[RotationKey].append(FinalState) + if self.getOEData and data != None: + self.OEData[RotationKey].append(data) + #except: + #print("!!!! Alert: Skipping", RotationKey,"-", i, "for Star", self.KeySystemID, "due to unforseen issues!") + #print("!!!! The Particle Set's IDs are as follows:", self.ICs[RotationKey][i].id) # Stop the NBody Codes if not Provided if self.kep == None: From b74c3bffab402e275ef097cbdc8a03ac2d4b24c5 Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 25 Aug 2020 19:02:27 -0400 Subject: [PATCH 129/130] Attempting fix. --- src/tycho/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tycho/util.py b/src/tycho/util.py index 08260a7..acb012e 100644 --- a/src/tycho/util.py +++ b/src/tycho/util.py @@ -279,7 +279,7 @@ def get_stellar_radius(star, SEVCode = None): if SEVCode == None: sev_code.stop() else: - sev_code.particles = Particles() + sev_code.particles.remove_particle(temp_star) return radius def resolve_supernova(supernova_detection, bodies, time): From 00829f2e12011b55cd6b0fba6e99d685f94ad36a Mon Sep 17 00:00:00 2001 From: Joseph Glaser Date: Tue, 25 Aug 2020 19:09:45 -0400 Subject: [PATCH 130/130] Fixed SEV resetting by deleting particle rather than setting it empty. --- src/tycho/scattering.py | 151 ++++++++++++++++++++-------------------- src/tycho/util.py | 2 +- 2 files changed, 77 insertions(+), 76 deletions(-) diff --git a/src/tycho/scattering.py b/src/tycho/scattering.py index 99a797d..5b43100 100644 --- a/src/tycho/scattering.py +++ b/src/tycho/scattering.py @@ -121,81 +121,82 @@ def SimAllEncounters(self): # Begin Looping over Rotation Keys ... for RotationKey in self.ICs.keys(): for i in range(len(self.ICs[RotationKey])): - #try: - print("!!!!!!", self.KeySystemID, RotationKey, i) - - # Identify the Current Encounter in the List for This Rotation - CurrentEncounter = self.ICs[RotationKey][i] - #print(CurrentEncounter[0].position) - - # Create the Encounter Instance with the Current Encounter - Encounter_Inst = self.SingleEncounter(CurrentEncounter) - - # Simulate the Encounter till the Encounter is Over via N-Body Integrator - # -OR- the time to the Next Encounter is Reached - if len(self.StartTimes[RotationKey]) == 1 or i+1 == len(self.StartTimes[RotationKey]): - current_max_endtime = self.max_end_time - else: - current_max_endtime = self.StartTimes[RotationKey][i+1] - EndingState = Encounter_Inst.SimSingleEncounter(current_max_endtime, \ - start_time = self.StartTimes[RotationKey][i], \ - GCodes = self.NBodyCodes) - EndingStateTime = np.max(np.unique(EndingState.time)) - - #print(Encounter_Inst.particles[0].position) - print("The Encounter was over after:", (EndingStateTime- self.StartTimes[RotationKey][i]).value_in(units.Myr)) - - - #print(EndingState.id, EndingState.x) - print('----------') - #print(Encounter_Inst.particles.id, Encounter_Inst.particles.x) - - # Strip off Anything Not Associated with the Key System - systems_in_current_encounter = stellar_systems.get_heirarchical_systems_from_set(EndingState, kepler_workers=self.kep) - - # Reassign the EndingState to include the Primary System ONLY - EndingState = systems_in_current_encounter[self.KeySystemID] - print("Before Secular:", EndingState.id, EndingState.x) - #print(EndingState[0].position) - - #print(len(self.ICs[RotationKey])-1) - # If Encounter Patching is Desired -AND- it isn't the last Encounter - if i + 1 < len(self.ICs[RotationKey]) and self.doEncounterPatching: - - # Identify the Next Encounter in the List - NextEncounter = self.ICs[RotationKey][i+1] - - # Simulate System till the Next Encounter's Start Time - Encounter_Inst = self.SingleEncounter(EndingState) - FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], \ - start_time = EndingStateTime, \ - GCode = self.SecularCode, getOEData=self.getOEData, \ - KeySystemID = self.KeySystemID, SCode=self.SEVCode) - if newcode != None: - self.SecularCode = newcode - print("After Secular:", FinalState.id) - # Begin Patching of the End State to the Next Encounter - self.ICs[RotationKey][i+1] = self.PatchedEncounter(FinalState, NextEncounter) - else: - # Simulate System till Desired Global Endtime - #print(CurrentEncounter[0].time.value_in(units.Myr)) - #print(EndingState[0].time.value_in(units.Myr)) - Encounter_Inst = self.SingleEncounter(EndingState) - FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.desired_endtime, \ - start_time = EndingStateTime, \ - GCode = self.SecularCode, getOEData=self.getOEData, \ - KeySystemID = self.KeySystemID, SCode=self.SEVCode) - if newcode != None: - self.SecularCode = newcode - print("After Secular:", FinalState.id) - - # Append the FinalState of Each Encounter to its Dictionary - self.FinalStates[RotationKey].append(FinalState) - if self.getOEData and data != None: - self.OEData[RotationKey].append(data) - #except: - #print("!!!! Alert: Skipping", RotationKey,"-", i, "for Star", self.KeySystemID, "due to unforseen issues!") - #print("!!!! The Particle Set's IDs are as follows:", self.ICs[RotationKey][i].id) + try: + print(util.timestamp(), "!!! UPDATE: Starting the following encounter", \ + "Star", self.KeySystemID, RotationKey, "-", i) + + # Identify the Current Encounter in the List for This Rotation + CurrentEncounter = self.ICs[RotationKey][i] + #print(CurrentEncounter[0].position) + + # Create the Encounter Instance with the Current Encounter + Encounter_Inst = self.SingleEncounter(CurrentEncounter) + + # Simulate the Encounter till the Encounter is Over via N-Body Integrator + # -OR- the time to the Next Encounter is Reached + if len(self.StartTimes[RotationKey]) == 1 or i+1 == len(self.StartTimes[RotationKey]): + current_max_endtime = self.max_end_time + else: + current_max_endtime = self.StartTimes[RotationKey][i+1] + EndingState = Encounter_Inst.SimSingleEncounter(current_max_endtime, \ + start_time = self.StartTimes[RotationKey][i], \ + GCodes = self.NBodyCodes) + EndingStateTime = np.max(np.unique(EndingState.time)) + + #print(Encounter_Inst.particles[0].position) + print("The Encounter was over after:", (EndingStateTime- self.StartTimes[RotationKey][i]).value_in(units.Myr)) + + + #print(EndingState.id, EndingState.x) + print('----------') + #print(Encounter_Inst.particles.id, Encounter_Inst.particles.x) + + # Strip off Anything Not Associated with the Key System + systems_in_current_encounter = stellar_systems.get_heirarchical_systems_from_set(EndingState, kepler_workers=self.kep) + + # Reassign the EndingState to include the Primary System ONLY + EndingState = systems_in_current_encounter[self.KeySystemID] + print("Before Secular:", EndingState.id, EndingState.x) + #print(EndingState[0].position) + + #print(len(self.ICs[RotationKey])-1) + # If Encounter Patching is Desired -AND- it isn't the last Encounter + if i + 1 < len(self.ICs[RotationKey]) and self.doEncounterPatching: + + # Identify the Next Encounter in the List + NextEncounter = self.ICs[RotationKey][i+1] + + # Simulate System till the Next Encounter's Start Time + Encounter_Inst = self.SingleEncounter(EndingState) + FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.StartTimes[RotationKey][i+1], \ + start_time = EndingStateTime, \ + GCode = self.SecularCode, getOEData=self.getOEData, \ + KeySystemID = self.KeySystemID, SCode=self.SEVCode) + if newcode != None: + self.SecularCode = newcode + print("After Secular:", FinalState.id) + # Begin Patching of the End State to the Next Encounter + self.ICs[RotationKey][i+1] = self.PatchedEncounter(FinalState, NextEncounter) + else: + # Simulate System till Desired Global Endtime + #print(CurrentEncounter[0].time.value_in(units.Myr)) + #print(EndingState[0].time.value_in(units.Myr)) + Encounter_Inst = self.SingleEncounter(EndingState) + FinalState, data, newcode = Encounter_Inst.SimSecularSystem(self.desired_endtime, \ + start_time = EndingStateTime, \ + GCode = self.SecularCode, getOEData=self.getOEData, \ + KeySystemID = self.KeySystemID, SCode=self.SEVCode) + if newcode != None: + self.SecularCode = newcode + print("After Secular:", FinalState.id) + + # Append the FinalState of Each Encounter to its Dictionary + self.FinalStates[RotationKey].append(FinalState) + if self.getOEData and data != None: + self.OEData[RotationKey].append(data) + except: + print("!!!! Alert: Skipping", RotationKey,"-", i, "for Star", self.KeySystemID, "due to unforseen issues!") + print("!!!! The Particle Set's IDs are as follows:", self.ICs[RotationKey][i].id) # Stop the NBody Codes if not Provided if self.kep == None: diff --git a/src/tycho/util.py b/src/tycho/util.py index acb012e..825abc0 100644 --- a/src/tycho/util.py +++ b/src/tycho/util.py @@ -275,7 +275,7 @@ def get_stellar_radius(star, SEVCode = None): sev_code.model_time = star.time sev_code.evolve_model(star.time) radius = sev_code.particles[0].radius - print(sev_code.particles[0].radius) + print(radius, sev_code.particles[0].age) if SEVCode == None: sev_code.stop() else: