def pos_shift( shell_radii, particles_per_shell, res_increase_factor=85, ): """ Return the relative positions shift for this shell as a numpy array. """ total_particles = sum(particles_per_shell) relative_positions = numpy.zeros( total_particles * 3, dtype=float, ).reshape(total_particles, 3) ipos = 1 for shell in range(1, len(shell_radii)): num_pts = particles_per_shell[shell] indices = numpy.arange(0, num_pts, dtype=float) + 0.5 phi = arccos(1 - 2 * indices / num_pts) theta = pi * (1 + 5**0.5) * indices ipos2 = ipos + num_pts x = shell_radii[shell] * cos(theta) * sin(phi) y = shell_radii[shell] * sin(theta) * sin(phi) z = shell_radii[shell] * cos(phi) relative_positions[ipos:ipos2, 0:3] = numpy.dstack((x, y, z)) ipos = ipos2 return relative_positions
def test31(self): """ test trigonometric unit stuff """ self.assertEqual(units.pi, numpy.pi) a = units.pi self.assertEqual(trigo.to_rad(a), numpy.pi | units.rad) self.assertEqual(trigo.to_deg(a), 180. | units.deg) self.assertEqual(trigo.to_rev(a), 0.5 | units.rev) a = 90 | units.deg self.assertEqual(trigo.to_rad(a), numpy.pi / 2 | units.rad) self.assertEqual(trigo.to_deg(a), 90. | units.deg) self.assertEqual(trigo.to_rev(a), 0.25 | units.rev) a = 0.75 | units.rev self.assertEqual(trigo.to_rad(a), 3 / 2. * numpy.pi | units.rad) self.assertEqual(trigo.to_deg(a), 270. | units.deg) self.assertEqual(trigo.to_rev(a), 0.75 | units.rev) a = 2 * numpy.pi self.assertEqual(trigo.to_rad(a), 2 * numpy.pi | units.rad) self.assertEqual(trigo.to_deg(a), 360. | units.deg) self.assertEqual(trigo.to_rev(a), 1. | units.rev) a = 45. | units.deg self.assertEqual(trigo.sin(a), numpy.sin(45. / 180 * numpy.pi)) self.assertEqual(trigo.cos(a), numpy.cos(45. / 180 * numpy.pi)) self.assertEqual(trigo.tan(a), numpy.tan(45. / 180 * numpy.pi)) a = 1. | units.rad self.assertEqual(trigo.sin(a), numpy.sin(1.)) self.assertEqual(trigo.cos(a), numpy.cos(1.)) self.assertEqual(trigo.tan(a), numpy.tan(1.)) a = 0.125 | units.rev self.assertEqual(trigo.sin(a), numpy.sin(45. / 180 * numpy.pi)) self.assertEqual(trigo.cos(a), numpy.cos(45. / 180 * numpy.pi)) self.assertEqual(trigo.tan(a), numpy.tan(45. / 180 * numpy.pi)) a = 45. | units.deg self.assertAlmostEqual(trigo.arcsin(trigo.sin(a)), 45. | units.deg, 13) self.assertAlmostEqual(trigo.arccos(trigo.cos(a)), 45. | units.deg, 13) self.assertAlmostEqual(trigo.arctan(trigo.tan(a)), 45. | units.deg, 13)
def test31(self): """ test trigonometric unit stuff """ self.assertEqual(units.pi,numpy.pi) a=units.pi self.assertEqual(trigo.to_rad(a), numpy.pi | units.rad) self.assertEqual(trigo.to_deg(a), 180. | units.deg) self.assertEqual(trigo.to_rev(a), 0.5 | units.rev) a=90 | units.deg self.assertEqual(trigo.to_rad(a), numpy.pi/2 | units.rad) self.assertEqual(trigo.to_deg(a), 90. | units.deg) self.assertEqual(trigo.to_rev(a), 0.25 | units.rev) a=0.75 | units.rev self.assertEqual(trigo.to_rad(a), 3/2.*numpy.pi | units.rad) self.assertEqual(trigo.to_deg(a), 270. | units.deg) self.assertEqual(trigo.to_rev(a), 0.75 | units.rev) a=2*numpy.pi self.assertEqual(trigo.to_rad(a), 2*numpy.pi | units.rad) self.assertEqual(trigo.to_deg(a), 360. | units.deg) self.assertEqual(trigo.to_rev(a), 1. | units.rev) a=45. | units.deg self.assertEqual(trigo.sin(a),numpy.sin(45./180*numpy.pi)) self.assertEqual(trigo.cos(a),numpy.cos(45./180*numpy.pi)) self.assertEqual(trigo.tan(a),numpy.tan(45./180*numpy.pi)) a=1. | units.rad self.assertEqual(trigo.sin(a),numpy.sin(1.)) self.assertEqual(trigo.cos(a),numpy.cos(1.)) self.assertEqual(trigo.tan(a),numpy.tan(1.)) a=0.125 | units.rev self.assertEqual(trigo.sin(a),numpy.sin(45./180*numpy.pi)) self.assertEqual(trigo.cos(a),numpy.cos(45./180*numpy.pi)) self.assertEqual(trigo.tan(a),numpy.tan(45./180*numpy.pi)) a=45. | units.deg self.assertAlmostEqual(trigo.arcsin(trigo.sin(a)),45. | units.deg,13) self.assertAlmostEqual(trigo.arccos(trigo.cos(a)),45. | units.deg,13) self.assertAlmostEqual(trigo.arctan(trigo.tan(a)),45. | units.deg,13)
def get_orbital_elements_from_arrays(rel_position_raw, rel_velocity_raw, total_masses, G=nbody_system.G): """ Orbital elements from array of relative positions and velocities vectors, based on orbital_elements_from_binary and adapted to work for arrays (each line characterises a two body problem). For circular orbits (eccentricity=0): returns argument of pericenter = 0., true anomaly = 0. For equatorial orbits (inclination=0): longitude of ascending node = 0, argument of pericenter = arctan2(e_y,e_x). :argument rel_position: array of vectors of relative positions of the two-body systems :argument rel_velocity: array of vectors of relative velocities of the two-body systems :argument total_masses: array of total masses for two-body systems :argument G: gravitational constant :output semimajor_axis: array of semi-major axes :output eccentricity: array of eccentricities :output period: array of orbital periods :output inc: array of inclinations [radians] :output long_asc_node: array of longitude of ascending nodes [radians] :output arg_per_mat: array of argument of pericenters [radians] :output true_anomaly: array of true anomalies [radians] """ if len(numpy.shape(rel_position_raw)) == 1: rel_position = numpy.zeros([1, 3]) * rel_position_raw[0] rel_position[0, 0] = rel_position_raw[0] rel_position[0, 1] = rel_position_raw[1] rel_position[0, 2] = rel_position_raw[2] rel_velocity = numpy.zeros([1, 3]) * rel_velocity_raw[0] rel_velocity[0, 0] = rel_velocity_raw[0] rel_velocity[0, 1] = rel_velocity_raw[1] rel_velocity[0, 2] = rel_velocity_raw[2] else: rel_position = rel_position_raw rel_velocity = rel_velocity_raw separation = (rel_position**2).sum(axis=1)**0.5 n_vec = len(rel_position) speed_squared = (rel_velocity**2).sum(axis=1) semimajor_axis = (G * total_masses * separation / (2. * G * total_masses - separation * speed_squared)) neg_ecc_arg = ( (to_quantity(rel_position).cross(rel_velocity)**2).sum(axis=-1) / (G * total_masses * semimajor_axis)) filter_ecc0 = (1. <= neg_ecc_arg) eccentricity = numpy.zeros(separation.shape) eccentricity[~filter_ecc0] = numpy.sqrt(1.0 - neg_ecc_arg[~filter_ecc0]) eccentricity[filter_ecc0] = 0. # angular momentum mom = to_quantity(rel_position).cross(rel_velocity) # inclination inc = arccos(mom[:, 2] / to_quantity(mom).lengths()) # Longitude of ascending nodes, with reference direction along x-axis asc_node_matrix_unit = numpy.zeros(rel_position.shape) z_vectors = numpy.zeros([n_vec, 3]) z_vectors[:, 2] = 1. z_vectors = z_vectors | units.none ascending_node_vectors = z_vectors.cross(mom) filter_non0_incl = (to_quantity(ascending_node_vectors).lengths().number > 0.) asc_node_matrix_unit[~filter_non0_incl] = numpy.array([1., 0., 0.]) an_vectors_len = to_quantity( ascending_node_vectors[filter_non0_incl]).lengths() asc_node_matrix_unit[filter_non0_incl] = normalize_vector( ascending_node_vectors[filter_non0_incl], an_vectors_len) long_asc_node = arctan2(asc_node_matrix_unit[:, 1], asc_node_matrix_unit[:, 0]) # Argument of periapsis using eccentricity a.k.a. Laplace-Runge-Lenz vector mu = G * total_masses pos_unit_vecs = normalize_vector(rel_position, separation) mom_len = to_quantity(mom).lengths() mom_unit_vecs = normalize_vector(mom, mom_len) e_vecs = (normalize_vector(to_quantity(rel_velocity).cross(mom), mu) - pos_unit_vecs) # Argument of pericenter cannot be determined for e = 0, # in this case return 0.0 and 1.0 for the cosines e_vecs_norm = (e_vecs**2).sum(axis=1)**0.5 filter_non0_ecc = (e_vecs_norm > 1.e-15) arg_per_mat = VectorQuantity(array=numpy.zeros(long_asc_node.shape), unit=units.rad) cos_arg_per = numpy.zeros(long_asc_node.shape) arg_per_mat[~filter_non0_ecc] = 0. | units.rad cos_arg_per[~filter_non0_ecc] = 1. e_vecs_unit = numpy.zeros(rel_position.shape) e_vecs_unit[filter_non0_ecc] = normalize_vector( e_vecs[filter_non0_ecc], e_vecs_norm[filter_non0_ecc]) cos_arg_per[filter_non0_ecc] = (e_vecs_unit[filter_non0_ecc] * asc_node_matrix_unit[filter_non0_ecc]).sum( axis=-1) e_cross_an = numpy.zeros(e_vecs_unit.shape) e_cross_an[filter_non0_ecc] = numpy.cross( e_vecs_unit[filter_non0_ecc], asc_node_matrix_unit[filter_non0_ecc]) e_cross_an_norm = (e_cross_an**2).sum(axis=1)**0.5 filter_non0_e_cross_an = (e_cross_an_norm != 0.) ss = -numpy.sign((mom_unit_vecs[filter_non0_e_cross_an] * e_cross_an[filter_non0_e_cross_an]).sum(axis=-1)) # note change in size in sin_arg_per and cos_arg_per; they are not used further sin_arg_per = ss * e_cross_an_norm[filter_non0_e_cross_an] cos_arg_per = cos_arg_per[filter_non0_e_cross_an] arg_per_mat[filter_non0_e_cross_an] = arctan2(sin_arg_per, cos_arg_per) # in case longitude of ascending node is 0, omega=arctan2(e_y,e_x) arg_per_mat[~filter_non0_e_cross_an & filter_non0_ecc] = (arctan2( e_vecs[~filter_non0_e_cross_an & filter_non0_ecc, 1], e_vecs[~filter_non0_e_cross_an & filter_non0_ecc, 0])) filter_negative_zmom = (~filter_non0_e_cross_an & filter_non0_ecc & (mom[:, 2] < 0. * mom[0, 0])) arg_per_mat[filter_negative_zmom] = (2. * numpy.pi - arg_per_mat[filter_negative_zmom]) # true anomaly cos_true_anomaly = (e_vecs_unit * pos_unit_vecs).sum(axis=-1) e_cross_pos = numpy.cross(e_vecs_unit, pos_unit_vecs) ss2 = numpy.sign((mom_unit_vecs * e_cross_pos).sum(axis=-1)) sin_true_anomaly = ss2 * (e_cross_pos**2).sum(axis=1)**0.5 true_anomaly = arctan2(sin_true_anomaly, cos_true_anomaly) return (semimajor_axis, eccentricity, true_anomaly, inc, long_asc_node, arg_per_mat)
def get_orbital_elements_from_arrays( rel_position_raw, rel_velocity_raw, total_masses, G=nbody_system.G): """ Orbital elements from array of relative positions and velocities vectors, based on orbital_elements_from_binary and adapted to work for arrays (each line characterises a two body problem). For circular orbits (eccentricity=0): returns argument of pericenter = 0., true anomaly = 0. For equatorial orbits (inclination=0): longitude of ascending node = 0, argument of pericenter = arctan2(e_y,e_x). :argument rel_position: array of vectors of relative positions of the two-body systems :argument rel_velocity: array of vectors of relative velocities of the two-body systems :argument total_masses: array of total masses for two-body systems :argument G: gravitational constant :output semimajor_axis: array of semi-major axes :output eccentricity: array of eccentricities :output period: array of orbital periods :output inc: array of inclinations [radians] :output long_asc_node: array of longitude of ascending nodes [radians] :output arg_per_mat: array of argument of pericenters [radians] :output true_anomaly: array of true anomalies [radians] """ if len(numpy.shape(rel_position_raw)) == 1: rel_position = numpy.zeros([1, 3]) * rel_position_raw[0] rel_position[0, 0] = rel_position_raw[0] rel_position[0, 1] = rel_position_raw[1] rel_position[0, 2] = rel_position_raw[2] rel_velocity = numpy.zeros([1, 3]) * rel_velocity_raw[0] rel_velocity[0, 0] = rel_velocity_raw[0] rel_velocity[0, 1] = rel_velocity_raw[1] rel_velocity[0, 2] = rel_velocity_raw[2] else: rel_position = rel_position_raw rel_velocity = rel_velocity_raw separation = (rel_position**2).sum(axis=1)**0.5 n_vec = len(rel_position) speed_squared = (rel_velocity**2).sum(axis=1) semimajor_axis = ( G * total_masses * separation / (2. * G * total_masses - separation * speed_squared) ) neg_ecc_arg = ( ( to_quantity(rel_position).cross(rel_velocity)**2 ).sum(axis=-1) / (G * total_masses * semimajor_axis) ) filter_ecc0 = (1. <= neg_ecc_arg) eccentricity = numpy.zeros(separation.shape) eccentricity[~filter_ecc0] = numpy.sqrt(1.0 - neg_ecc_arg[~filter_ecc0]) eccentricity[filter_ecc0] = 0. # angular momentum mom = to_quantity(rel_position).cross(rel_velocity) # inclination inc = arccos(mom[:, 2]/to_quantity(mom).lengths()) # Longitude of ascending nodes, with reference direction along x-axis asc_node_matrix_unit = numpy.zeros(rel_position.shape) z_vectors = numpy.zeros([n_vec, 3]) z_vectors[:, 2] = 1. z_vectors = z_vectors | units.none ascending_node_vectors = z_vectors.cross(mom) filter_non0_incl = ( to_quantity(ascending_node_vectors).lengths().number > 0.) asc_node_matrix_unit[~filter_non0_incl] = numpy.array([1., 0., 0.]) an_vectors_len = to_quantity( ascending_node_vectors[filter_non0_incl]).lengths() asc_node_matrix_unit[filter_non0_incl] = normalize_vector( ascending_node_vectors[filter_non0_incl], an_vectors_len) long_asc_node = arctan2( asc_node_matrix_unit[:, 1], asc_node_matrix_unit[:, 0]) # Argument of periapsis using eccentricity a.k.a. Laplace-Runge-Lenz vector mu = G*total_masses pos_unit_vecs = normalize_vector(rel_position, separation) mom_len = to_quantity(mom).lengths() mom_unit_vecs = normalize_vector(mom, mom_len) e_vecs = ( normalize_vector( to_quantity(rel_velocity).cross(mom), mu) - pos_unit_vecs ) # Argument of pericenter cannot be determined for e = 0, # in this case return 0.0 and 1.0 for the cosines e_vecs_norm = (e_vecs**2).sum(axis=1)**0.5 filter_non0_ecc = (e_vecs_norm > 1.e-15) arg_per_mat = VectorQuantity( array=numpy.zeros(long_asc_node.shape), unit=units.rad) cos_arg_per = numpy.zeros(long_asc_node.shape) arg_per_mat[~filter_non0_ecc] = 0. | units.rad cos_arg_per[~filter_non0_ecc] = 1. e_vecs_unit = numpy.zeros(rel_position.shape) e_vecs_unit[filter_non0_ecc] = normalize_vector( e_vecs[filter_non0_ecc], e_vecs_norm[filter_non0_ecc] ) cos_arg_per[filter_non0_ecc] = ( e_vecs_unit[filter_non0_ecc] * asc_node_matrix_unit[filter_non0_ecc] ).sum(axis=-1) e_cross_an = numpy.zeros(e_vecs_unit.shape) e_cross_an[filter_non0_ecc] = numpy.cross( e_vecs_unit[filter_non0_ecc], asc_node_matrix_unit[filter_non0_ecc] ) e_cross_an_norm = (e_cross_an**2).sum(axis=1)**0.5 filter_non0_e_cross_an = (e_cross_an_norm != 0.) ss = -numpy.sign( ( mom_unit_vecs[filter_non0_e_cross_an] * e_cross_an[filter_non0_e_cross_an] ).sum(axis=-1) ) # note change in size in sin_arg_per and cos_arg_per; they are not used further sin_arg_per = ss*e_cross_an_norm[filter_non0_e_cross_an] cos_arg_per = cos_arg_per[filter_non0_e_cross_an] arg_per_mat[filter_non0_e_cross_an] = arctan2(sin_arg_per, cos_arg_per) # in case longitude of ascending node is 0, omega=arctan2(e_y,e_x) arg_per_mat[~filter_non0_e_cross_an & filter_non0_ecc] = ( arctan2( e_vecs[~filter_non0_e_cross_an & filter_non0_ecc, 1], e_vecs[~filter_non0_e_cross_an & filter_non0_ecc, 0] ) ) filter_negative_zmom = ( ~filter_non0_e_cross_an & filter_non0_ecc & (mom[:, 2] < 0.*mom[0, 0]) ) arg_per_mat[filter_negative_zmom] = ( 2. * numpy.pi - arg_per_mat[filter_negative_zmom] ) # true anomaly cos_true_anomaly = (e_vecs_unit*pos_unit_vecs).sum(axis=-1) e_cross_pos = numpy.cross(e_vecs_unit, pos_unit_vecs) ss2 = numpy.sign((mom_unit_vecs*e_cross_pos).sum(axis=-1)) sin_true_anomaly = ss2*(e_cross_pos**2).sum(axis=1)**0.5 true_anomaly = arctan2(sin_true_anomaly, cos_true_anomaly) return ( semimajor_axis, eccentricity, true_anomaly, inc, long_asc_node, arg_per_mat )