Пример #1
0
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
Пример #2
0
    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)
Пример #3
0
    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)
Пример #4
0
    def delta_positions_and_velocities(new_stars, star_forming_regions,
                                       probabilities):
        """
        Assign positions and velocities of stars in the star forming regions
        according to the probability distribution
        """
        number_of_stars = len(new_stars)

        # Create an index list of removed gas from probability list
        sample = numpy.random.choice(len(star_forming_regions),
                                     number_of_stars,
                                     p=probabilities)

        # Assign the stars to the removed gas according to the sample
        star_forming_regions_sampled = star_forming_regions[sample]
        new_stars.position = star_forming_regions_sampled.position
        new_stars.velocity = star_forming_regions_sampled.velocity
        new_stars.origin_cloud = star_forming_regions_sampled.accreted_by_sink
        new_stars.star_forming_radius = star_forming_regions_sampled.radius
        try:
            new_stars.star_forming_u = star_forming_regions_sampled.u
        except AttributeError:
            new_stars.star_forming_u = local_sound_speed**2

        # Random position of stars within the sink radius they assigned to
        rho = (numpy.random.random(number_of_stars) *
               new_stars.star_forming_radius)
        theta = (numpy.random.random(number_of_stars) *
                 (2 * numpy.pi | units.rad))
        phi = (numpy.random.random(number_of_stars) * numpy.pi | units.rad)
        x = (rho * sin(phi) * cos(theta)).value_in(units.pc)
        y = (rho * sin(phi) * sin(theta)).value_in(units.pc)
        z = (rho * cos(phi)).value_in(units.pc)

        X = list(zip(*[x, y, z])) | units.pc

        # Random velocity, sample magnitude from gaussian with local sound
        # speed like Wall et al (2019)
        # temperature = 10 | units.K

        # or (gamma * local_pressure / density).sqrt()
        velocity_magnitude = numpy.random.normal(
            # loc=0.0,  # <- since we already added the velocity of the sink
            scale=new_stars.star_forming_u.sqrt().value_in(units.kms),
            size=number_of_stars,
        ) | units.kms
        velocity_theta = (numpy.random.random(number_of_stars) *
                          (2 * numpy.pi | units.rad))
        velocity_phi = (numpy.random.random(number_of_stars) *
                        (numpy.pi | units.rad))
        vx = (velocity_magnitude * sin(velocity_phi) *
              cos(velocity_theta)).value_in(units.kms)
        vy = (velocity_magnitude * sin(velocity_phi) *
              sin(velocity_theta)).value_in(units.kms)
        vz = (velocity_magnitude * cos(velocity_phi)).value_in(units.kms)

        V = list(zip(*[vx, vy, vz])) | units.kms

        return X, V
Пример #5
0
    def get_potential_at_point(self, eps, x, y, z):
        """
        Calculate the spiral arm potential at specified point

        from Cox & Gomez 2002
        Input: eps, x, y, z
        Returns: potential
        """

        phi = arctan(y / x)

        # if x < (0 | units.parsec):
        #     phi = pi + phi

        # d2
        r = (x**2 + y**2)**0.5

        gamma = (self.number_of_arms *
                 (phi + self.phir * (self.time_initial + self.model_time) -
                  log(r / self.fiducial_radius) / tan(self.pitch_angle)))

        result = 0 | units.parsec
        for n in range(3):
            Kn = (n + 1) * self.number_of_arms / (r * sin(self.pitch_angle))
            Bn = Kn * self.scale_height * (1 + 0.4 * Kn * self.scale_height)
            Dn = (1 + Kn * self.scale_height + 0.3 *
                  (Kn * self.scale_height)**2) / (1 +
                                                  0.3 * Kn * self.scale_height)

            result = (result + (self.Cz[n] / (Dn * Kn)) * cos(
                (n + 1) * gamma) * (1 / cosh((Kn * z) / Bn))**Bn)

        spiral_value = (
            -4 * pi * G * self.scale_height * self.density_at_fiducial_radius *
            exp(-(r - self.fiducial_radius) / self.radial_scale_length) *
            result)
        return spiral_value.in_(units.parsec**2 * units.Myr**-2)
Пример #6
0
def new_rotation_matrix(phi, theta, psi):
    """
    Return the rotation matrix, to rotate positions, around the x-axis (phi), y-axis (theta) and z-axis (psi).
    See wikipedia for reference
    """
    
    return numpy.array( (
        (cos(theta)*cos(psi), -cos(phi)*sin(psi) + sin(phi)*sin(theta)*cos(psi),  sin(phi)*sin(psi) + cos(phi)*sin(theta)*cos(psi)) ,
        (cos(theta)*sin(psi),  cos(phi)*cos(psi) + sin(phi)*sin(theta)*sin(psi), -sin(phi)*cos(psi) + cos(phi)*sin(theta)*sin(psi)) ,
        (-sin(theta)        ,  sin(phi)*cos(theta)                             ,  cos(phi)*cos(theta))
    ) )
Пример #7
0
def true_anomaly_from_eccentric_anomaly(E, e):
    return 2 * arctan2((1 + e)**0.5 * sin(E / 2), (1 - e)**0.5 * cos(E / 2))
Пример #8
0
def rel_posvel_arrays_from_orbital_elements(
        primary_mass,
        secondary_mass,
        semi_major_axis,
        eccentricity=0 | units.rad,
        true_anomaly=0 | units.rad,
        inclination=0 | units.rad,
        longitude_of_the_ascending_node=0 | units.rad,
        argument_of_periapsis=0 | units.rad,
        G=nbody_system.G):
    """
    Returns relative positions/velocities for secondaries orbiting primaries.
    If primary_mass is a scalar, assumes the same primary for all secondaries.
    """
    try:
        number_of_secondaries = len(secondary_mass)
    except:
        number_of_secondaries = 1

    # arrays need to be equal to number of secondaries, or have just one value
    primary_mass = equal_length_array_or_scalar(primary_mass,
                                                length=number_of_secondaries)
    semi_major_axis = equal_length_array_or_scalar(
        semi_major_axis, length=number_of_secondaries)
    eccentricity = equal_length_array_or_scalar(eccentricity,
                                                length=number_of_secondaries)
    true_anomaly = equal_length_array_or_scalar(true_anomaly,
                                                length=number_of_secondaries)
    inclination = equal_length_array_or_scalar(inclination,
                                               length=number_of_secondaries)
    longitude_of_the_ascending_node = equal_length_array_or_scalar(
        longitude_of_the_ascending_node, length=number_of_secondaries)
    argument_of_periapsis = equal_length_array_or_scalar(
        argument_of_periapsis, length=number_of_secondaries)

    cos_true_anomaly = cos(true_anomaly)
    sin_true_anomaly = sin(true_anomaly)

    cos_inclination = cos(inclination)
    sin_inclination = sin(inclination)

    cos_arg_per = cos(argument_of_periapsis)
    sin_arg_per = sin(argument_of_periapsis)

    cos_long_asc_nodes = cos(longitude_of_the_ascending_node)
    sin_long_asc_nodes = sin(longitude_of_the_ascending_node)

    # alpha is a unit vector directed along the line of node
    alphax = (cos_long_asc_nodes * cos_arg_per -
              sin_long_asc_nodes * sin_arg_per * cos_inclination)
    alphay = (sin_long_asc_nodes * cos_arg_per +
              cos_long_asc_nodes * sin_arg_per * cos_inclination)
    alphaz = sin_arg_per * sin_inclination
    alpha = numpy.array([alphax, alphay, alphaz])

    # beta is a unit vector perpendicular to alpha and the orbital angular
    # momentum vector
    betax = (-cos_long_asc_nodes * sin_arg_per -
             sin_long_asc_nodes * cos_arg_per * cos_inclination)
    betay = (-sin_long_asc_nodes * sin_arg_per +
             cos_long_asc_nodes * cos_arg_per * cos_inclination)
    betaz = cos_arg_per * sin_inclination
    beta = numpy.array([betax, betay, betaz])

    # Relative position and velocity
    separation = (  # Compute the relative separation
        semi_major_axis * (1.0 - eccentricity**2) /
        (1.0 + eccentricity * cos_true_anomaly))
    position_vector = (separation * cos_true_anomaly * alpha +
                       separation * sin_true_anomaly * beta).T
    velocity_tilde = (
        (G * (primary_mass + secondary_mass) /
         (semi_major_axis * (1.0 - eccentricity**2)))**0.5)  # Common factor
    velocity_vector = (-1.0 * velocity_tilde * sin_true_anomaly * alpha +
                       velocity_tilde *
                       (eccentricity + cos_true_anomaly) * beta).T

    return position_vector, velocity_vector
Пример #9
0
def swan_eq(**kwargs):

    grav = kwargs["grav"]
    rho_air = kwargs["rho_air"]
    rho_water = kwargs["rho_water"]
    flow = kwargs["flow"]
    fhigh = kwargs["fhigh"]
    u10 = kwargs["u10"]
    udir = kwargs["udir"]
    gridname = kwargs["gridname"]
    coord = kwargs["coord"]
    depth = kwargs["depth"]
    visc = kwargs["viscosity"]
    planetary_radius = kwargs["planetary_radius"]
    pixel_scale = kwargs["pixel_scale"]
    under_relaxation_factor = kwargs["under_relaxation_factor"]
    msc = kwargs["msc"]
    mdc = kwargs["mdc"]

    print(kwargs)

    nodes = read_set_from_file("kraken_nodes", "amuse", close_file=True)
    elements = read_set_from_file("kraken_elements", "amuse", close_file=True)

    s = Swan(grid_type="unstructured",
             input_grid_type="unstructured",
             redirection="none",
             coordinates=coord)

    s.parameters.gravitational_acceleration = grav
    s.parameters.air_density = rho_air
    s.parameters.water_density = rho_water
    s.parameters.planetary_radius = planetary_radius
    s.parameters.maximum_error_level = 2
    s.parameters.under_relaxation_factor = under_relaxation_factor

    ncells = len(elements)
    nverts = len(nodes)

    s.parameters.number_of_cells = ncells
    s.parameters.number_of_vertices = nverts
    s.parameters.number_of_directions = mdc
    s.parameters.number_of_frequencies = msc
    s.parameters.lowest_frequency = flow
    s.parameters.highest_frequency = fhigh
    s.parameters.minimum_wind_speed = 0.1 | units.m / units.s
    s.parameters.max_iterations_stationary = 50
    s.parameters.use_gen3_parameters = True
    s.parameters.use_breaking_parameters = True
    s.parameters.use_triads_parameters = True
    s.parameters.use_friction_parameters = True
    s.parameters.use_input_wind = True

    if visc.number > 0:
        s.parameters.use_input_turbulent_visc = True
        s.parameters.turbulent_viscosity_factor = 1.

    #~ s.parameters.uniform_wind_velocity=u10
    #~ s.parameters.uniform_wind_direction=udir

    print(s.parameters)
    #~ raise

    channel = nodes.new_channel_to(s.nodes)
    if coord == "spherical":
        channel.copy_attributes(["lon", "lat", "vmark"])
    else:
        channel.transform(["x", "y", "vmark"], lambda x, y, z:
                          (x * pixel_scale, y * pixel_scale, z),
                          ["x", "y", "vmark"])
        #~ channel.copy_attributes(["x","y","vmark"])
    channel = elements.new_channel_to(s.elements)
    channel.copy_attributes(["n1", "n2", "n3"])

    #~ print abs(s.nodes.lon-nodes.lon).max()
    #~ print abs(s.nodes.lat-nodes.lat).max()

    forcings = s.forcings.empty_copy()

    if depth == "default":
        forcings.depth = nodes.depth
    elif depth == "x2":
        forcings.depth = nodes.depth * 2
    elif depth == "x10":
        forcings.depth = nodes.depth * 10
    elif depth == "sqrt":
        maxdepth = nodes.depth.max()
        forcings.depth = maxdepth * (nodes.depth / maxdepth)**0.5
    else:
        raise Exception("unknown depth option")

    wind_vx = u10 * cos(udir)
    wind_vy = u10 * sin(udir)

    if coord == "spherical":
        forcings.wind_vx = wind_vx
        forcings.wind_vy = wind_vy
    else:
        forcings.wind_vx = wind_vx * sin(nodes.lon) + wind_vy * cos(nodes.lon)
        forcings.wind_vy = -wind_vx * cos(nodes.lon) + wind_vy * sin(nodes.lon)

        #~ pyplot.quiver(s.forcings.x.number,s.forcings.y.number,
        #~ forcings.wind_vx.number,forcings.wind_vy.number, scale=50)
        #~ pyplot.show()
        #~ raise

    if visc.number > 0:
        forcings.visc = visc
        forcings.new_channel_to(s.forcings).copy_attributes(
            ["depth", "wind_vx", "wind_vy", "visc"])
    else:
        forcings.new_channel_to(s.forcings).copy_attributes(
            ["depth", "wind_vx", "wind_vy"])

    s.evolve_model(0. | units.s)

    label = runlabel(**kwargs)
    print("label:", label)
    with open("args_" + label + ".pkl", "w") as f:
        pickle.dump(kwargs, f)
    write_set_to_file(s.nodes,
                      gridname + "_nodes_eq_" + label + ".amuse",
                      "amuse",
                      append_to_file=False)
Пример #10
0
def new_rotation_matrix(phi, theta, psi):
    """
    Return the rotation matrix, to rotate positions, around the x-axis (phi), y-axis (theta) and z-axis (psi).
    See wikipedia for reference
    """

    return numpy.array(
        ((cos(theta) * cos(psi),
          -cos(phi) * sin(psi) + sin(phi) * sin(theta) * cos(psi),
          sin(phi) * sin(psi) + cos(phi) * sin(theta) * cos(psi)),
         (cos(theta) * sin(psi),
          cos(phi) * cos(psi) + sin(phi) * sin(theta) * sin(psi),
          -sin(phi) * cos(psi) + cos(phi) * sin(theta) * sin(psi)),
         (-sin(theta), sin(phi) * cos(theta), cos(phi) * cos(theta))))
Пример #11
0
def new_stars_from_sink(origin,
                        upper_mass_limit=125 | units.MSun,
                        lower_mass_limit=0.1 | units.MSun,
                        default_radius=0.25 | units.pc,
                        velocity_dispersion=1 | units.kms,
                        logger=None,
                        initial_mass_function="kroupa",
                        distribution="random",
                        randomseed=None,
                        **keyword_arguments):
    """
    Form stars from an origin particle that keeps track of the properties of
    this region.
    """
    logger = logger or logging.getLogger(__name__)
    if randomseed is not None:
        logger.info("setting random seed to %i", randomseed)
        numpy.random.seed(randomseed)

    try:
        initialised = origin.initialised
    except AttributeError:
        initialised = False
    if not initialised:
        logger.debug("Initialising origin particle %i for star formation",
                     origin.key)
        next_mass = new_star_cluster(
            initial_mass_function=initial_mass_function,
            upper_mass_limit=upper_mass_limit,
            lower_mass_limit=lower_mass_limit,
            number_of_stars=1,
            **keyword_arguments)
        origin.next_primary_mass = next_mass[0].mass
        origin.initialised = True

    if origin.mass < origin.next_primary_mass:
        logger.debug(
            "Not enough in star forming region %i to form the next star",
            origin.key)
        return Particles()

    mass_reservoir = origin.mass - origin.next_primary_mass
    stellar_masses = new_star_cluster(
        stellar_mass=mass_reservoir,
        upper_mass_limit=upper_mass_limit,
        lower_mass_limit=lower_mass_limit,
        imf=initial_mass_function,
    ).mass
    number_of_stars = len(stellar_masses)

    new_stars = Particles(number_of_stars)
    new_stars.age = 0 | units.yr
    new_stars[0].mass = origin.next_primary_mass
    new_stars[1:].mass = stellar_masses[:-1]
    origin.next_primary_mass = stellar_masses[-1]
    new_stars.position = origin.position
    new_stars.velocity = origin.velocity

    try:
        radius = origin.radius
    except AttributeError:
        radius = default_radius
    rho = numpy.random.random(number_of_stars) * radius
    theta = (numpy.random.random(number_of_stars) * (2 * numpy.pi | units.rad))
    phi = (numpy.random.random(number_of_stars) * numpy.pi | units.rad)
    x = rho * sin(phi) * cos(theta)
    y = rho * sin(phi) * sin(theta)
    z = rho * cos(phi)
    new_stars.x += x
    new_stars.y += y
    new_stars.z += z

    velocity_magnitude = numpy.random.normal(
        scale=velocity_dispersion.value_in(units.kms),
        size=number_of_stars,
    ) | units.kms
    velocity_theta = (numpy.random.random(number_of_stars) *
                      (2 * numpy.pi | units.rad))
    velocity_phi = (numpy.random.random(number_of_stars) *
                    (numpy.pi | units.rad))
    vx = velocity_magnitude * sin(velocity_phi) * cos(velocity_theta)
    vy = velocity_magnitude * sin(velocity_phi) * sin(velocity_theta)
    vz = velocity_magnitude * cos(velocity_phi)
    new_stars.vx += vx
    new_stars.vy += vy
    new_stars.vz += vz

    new_stars.origin = origin.key
    origin.mass -= new_stars.total_mass()

    return new_stars
Пример #12
0
def true_anomaly_from_eccentric_anomaly(E, e):
    return 2*arctan2((1+e)**0.5*sin(E/2), (1-e)**0.5*cos(E/2))
Пример #13
0
def rel_posvel_arrays_from_orbital_elements(
        primary_mass,
        secondary_mass,
        semi_major_axis,
        eccentricity=0 | units.rad,
        true_anomaly=0 | units.rad,
        inclination=0 | units.rad,
        longitude_of_the_ascending_node=0 | units.rad,
        argument_of_periapsis=0 | units.rad,
        G=nbody_system.G
        ):
    """
    Returns relative positions/velocities for secondaries orbiting primaries.
    If primary_mass is a scalar, assumes the same primary for all secondaries.
    """
    try:
        number_of_secondaries = len(secondary_mass)
    except:
        number_of_secondaries = 1

    # arrays need to be equal to number of secondaries, or have just one value
    primary_mass = equal_length_array_or_scalar(
            primary_mass, length=number_of_secondaries)
    semi_major_axis = equal_length_array_or_scalar(
            semi_major_axis, length=number_of_secondaries)
    eccentricity = equal_length_array_or_scalar(
            eccentricity, length=number_of_secondaries)
    true_anomaly = equal_length_array_or_scalar(
            true_anomaly, length=number_of_secondaries)
    inclination = equal_length_array_or_scalar(
            inclination, length=number_of_secondaries)
    longitude_of_the_ascending_node = equal_length_array_or_scalar(
            longitude_of_the_ascending_node, length=number_of_secondaries)
    argument_of_periapsis = equal_length_array_or_scalar(
            argument_of_periapsis, length=number_of_secondaries)

    cos_true_anomaly = cos(true_anomaly)
    sin_true_anomaly = sin(true_anomaly)

    cos_inclination = cos(inclination)
    sin_inclination = sin(inclination)

    cos_arg_per = cos(argument_of_periapsis)
    sin_arg_per = sin(argument_of_periapsis)

    cos_long_asc_nodes = cos(longitude_of_the_ascending_node)
    sin_long_asc_nodes = sin(longitude_of_the_ascending_node)

    # alpha is a unit vector directed along the line of node
    alphax = (
            cos_long_asc_nodes*cos_arg_per
            - sin_long_asc_nodes*sin_arg_per*cos_inclination
            )
    alphay = (
            sin_long_asc_nodes*cos_arg_per
            + cos_long_asc_nodes*sin_arg_per*cos_inclination
            )
    alphaz = sin_arg_per*sin_inclination
    alpha = numpy.array([alphax, alphay, alphaz])

    # beta is a unit vector perpendicular to alpha and the orbital angular
    # momentum vector
    betax = (
            - cos_long_asc_nodes*sin_arg_per
            - sin_long_asc_nodes*cos_arg_per*cos_inclination
            )
    betay = (
            - sin_long_asc_nodes*sin_arg_per
            + cos_long_asc_nodes*cos_arg_per*cos_inclination
            )
    betaz = cos_arg_per*sin_inclination
    beta = numpy.array([betax, betay, betaz])

    # Relative position and velocity
    separation = (  # Compute the relative separation
            semi_major_axis*(1.0 - eccentricity**2)
            / (1.0 + eccentricity*cos_true_anomaly)
            )
    position_vector = (
            separation*cos_true_anomaly*alpha
            + separation*sin_true_anomaly*beta
            ).T
    velocity_tilde = (
            (
                G*(primary_mass + secondary_mass)
                / (semi_major_axis*(1.0 - eccentricity**2))
                )**0.5
            )  # Common factor
    velocity_vector = (
            -1.0 * velocity_tilde * sin_true_anomaly * alpha
            + velocity_tilde*(eccentricity + cos_true_anomaly)*beta
            ).T

    return position_vector, velocity_vector
Пример #14
0
def form_stars(sink,
               initial_mass_function=settings.stars_initial_mass_function,
               lower_mass_limit=settings.stars_lower_mass_limit,
               upper_mass_limit=settings.stars_upper_mass_limit,
               local_sound_speed=0.2 | units.kms,
               logger=None,
               randomseed=None,
               **keyword_arguments):
    """
    Let a sink form stars.
    """

    logger = logger or logging.getLogger(__name__)
    if randomseed is not None:
        logger.info("setting random seed to %i", randomseed)
        numpy.random.seed(randomseed)

    # sink_initial_density = sink.mass / (4/3 * numpy.pi * sink.radius**3)

    initialised = sink.initialised or False
    if not initialised:
        logger.debug("Initialising sink %i for star formation", sink.key)
        next_mass = generate_next_mass(
            initial_mass_function=initial_mass_function,
            lower_mass_limit=lower_mass_limit,
            upper_mass_limit=upper_mass_limit,
        )
        # sink.next_number_of_stars = len(next_mass)
        # sink.next_total_mass = next_mass.sum()
        sink.next_primary_mass = next_mass[0]
        # if sink.next_number_of_stars > 1:
        #     sink.next_secondary_mass = next_mass[1]
        # if sink.next_number_of_stars > 2:
        #     sink.next_tertiary_mass = next_mass[2]
        sink.initialised = True
    if sink.mass < sink.next_primary_mass:
        logger.debug("Sink %i is not massive enough for the next star",
                     sink.key)
        return [sink, Particles()]

    # We now have the first star that will be formed.
    # Next, we generate a list of stellar masses, so that the last star in the
    # list is just one too many for the sink's mass.

    mass_left = sink.mass - sink.next_primary_mass
    masses = new_masses(
        stellar_mass=mass_left,
        lower_mass_limit=lower_mass_limit,
        upper_mass_limit=upper_mass_limit,
        initial_mass_function=settings.stars_initial_mass_function,
    )
    number_of_stars = len(masses)

    new_stars = Particles(number_of_stars)
    new_stars.age = 0 | units.Myr
    new_stars[0].mass = sink.next_primary_mass
    new_stars[1:].mass = masses[:-1]
    sink.next_primary_mass = masses[-1]
    # if sink.next_number_of_stars > 1:
    #     new_stars[1].mass = sink.next_secondary_mass
    # if sink.next_number_of_stars > 2:
    #     new_stars[2].mass = sink.next_tertiary_mass
    new_stars.position = sink.position
    new_stars.velocity = sink.velocity

    # Random position within the sink radius
    radius = sink.radius
    rho = numpy.random.random(number_of_stars) * radius
    theta = (numpy.random.random(number_of_stars) * (2 * numpy.pi | units.rad))
    phi = (numpy.random.random(number_of_stars) * numpy.pi | units.rad)
    x = rho * sin(phi) * cos(theta)
    y = rho * sin(phi) * sin(theta)
    z = rho * cos(phi)
    new_stars.x += x
    new_stars.y += y
    new_stars.z += z
    # Random velocity, sample magnitude from gaussian with local sound speed
    # like Wall et al (2019)
    # temperature = 10 | units.K
    try:
        local_sound_speed = sink.u.sqrt()
    except AttributeError:
        local_sound_speed = local_sound_speed
    # or (gamma * local_pressure / density).sqrt()
    velocity_magnitude = numpy.random.normal(
        # loc=0.0,  # <- since we already added the velocity of the sink
        scale=local_sound_speed.value_in(units.kms),
        size=number_of_stars,
    ) | units.kms
    velocity_theta = (numpy.random.random(number_of_stars) *
                      (2 * numpy.pi | units.rad))
    velocity_phi = (numpy.random.random(number_of_stars) *
                    (numpy.pi | units.rad))
    vx = velocity_magnitude * sin(velocity_phi) * cos(velocity_theta)
    vy = velocity_magnitude * sin(velocity_phi) * sin(velocity_theta)
    vz = velocity_magnitude * cos(velocity_phi)
    new_stars.vx += vx
    new_stars.vy += vy
    new_stars.vz += vz

    new_stars.origin_cloud = sink.key
    # For Pentacle, this is the PP radius
    new_stars.radius = 0.05 | units.parsec
    sink.mass -= new_stars.total_mass()
    # TODO: fix sink's momentum etc

    # EDIT: Do not shrink the sinks at this point, but rather when finished
    # forming stars.
    # # Shrink the sink's (accretion) radius to prevent it from accreting
    # # relatively far away gas and moving a lot
    # sink.radius = (
    #     (sink.mass / sink_initial_density)
    #     / (4/3 * numpy.pi)
    # )**(1/3)

    # cleanup
    # sink.initialised = False
    new_stars.birth_mass = new_stars.mass
    return [sink, new_stars]
Пример #15
0
def form_stars_from_group(group_index,
                          sink_particles,
                          lower_mass_limit=settings.stars_lower_mass_limit,
                          upper_mass_limit=settings.stars_upper_mass_limit,
                          local_sound_speed=0.2 | units.kms,
                          minimum_sink_mass=0.01 | units.MSun,
                          logger=None,
                          randomseed=None,
                          shrink_sinks=True,
                          **keyword_arguments):
    """
    Last reviwed on 27 Nov 2020.

    Form stars from specific group of sinks.
    """
    logger = logger or logging.getLogger(__name__)
    #logger.info(
    #    "Using form_stars_from_group on group %i",
    #    group_index
    #)
    if randomseed is not None:
        logger.info("Setting random seed to %i", randomseed)
        numpy.random.seed(randomseed)

    # Sanity check: each sink particle must be in a group.
    ungrouped_sinks = sink_particles.select_array(lambda x: x <= 0,
                                                  ['in_group'])
    if not ungrouped_sinks.is_empty():
        logger.info(
            "WARNING: There exist ungrouped sinks. Something is wrong!")
        return None

    # Consider only group with input group index from here onwards.
    group = sink_particles[sink_particles.in_group == group_index]

    # Sanity check: group must have at least a sink
    if group.is_empty():
        logger.info(
            "WARNING: There is no sink in the group: Something is wrong!")
        return None

    number_of_sinks = len(group)
    group_mass = group.total_mass()
    logger.info("%i sinks found in group #%i with total mass %s",
                number_of_sinks, group_index, group_mass.in_(units.MSun))

    next_mass = generate_next_mass(
        initial_mass_function=initial_mass_function,
        lower_mass_limit=lower_mass_limit,
        upper_mass_limit=upper_mass_limit,
    )[0][0]
    try:
        # Within a group, group_next_primary_mass values are either
        # a mass, or 0 MSun. If all values are 0 MSun, this is a
        # new group. Else, only interested on the non-zero value. The
        # non-zero values are the same.

        #logger.info(
        #    'SANITY CHECK: group_next_primary_mass %s',
        #    group.group_next_primary_mass
        #)
        if group.group_next_primary_mass.max() == 0 | units.MSun:
            logger.info('Initiate group #%i for star formation', group_index)
            group.group_next_primary_mass = next_mass
        else:
            next_mass = group.group_next_primary_mass.max()
    # This happens for the first ever assignment of this attribute
    except AttributeError:
        logger.info(
            'AttributeError exception: Initiate group #%i for star formation',
            group_index)
        group.group_next_primary_mass = next_mass

    #logger.info("Next mass is %s", next_mass)

    if group_mass < next_mass:
        logger.info("Group #%i is not massive enough for the next star %s",
                    group_index, next_mass.in_(units.MSun))
        return None

    # Form stars from the leftover group sink mass
    mass_left = group_mass - next_mass
    masses = new_masses(
        stellar_mass=mass_left,
        lower_mass_limit=lower_mass_limit,
        upper_mass_limit=upper_mass_limit,
        initial_mass_function=settings.stars_initial_mass_function)
    number_of_stars = len(masses)

    #logger.info(
    #    "%i stars created in group #%i with %i sinks",
    #    number_of_stars, group_index, number_of_sinks
    #)

    new_stars = Particles(number_of_stars)
    new_stars.age = 0 | units.Myr
    new_stars[0].mass = next_mass
    new_stars[1:].mass = masses[:-1]
    group.group_next_primary_mass = masses[-1]
    new_stars = new_stars.sorted_by_attribute("mass").reversed()
    new_stars.in_group = group_index

    # Create placeholders for attributes of new_stars
    new_stars.position = [0, 0, 0] | units.pc
    new_stars.velocity = [0, 0, 0] | units.kms
    new_stars.origin_cloud = group[0].key
    new_stars.star_forming_radius = 0 | units.pc
    new_stars.star_forming_u = local_sound_speed**2

    #logger.info(
    #    "Group's next primary mass is %s",
    #    group.group_next_primary_mass[0]
    #)

    # Don't mess with the actual group sink particle set.
    star_forming_regions = group.copy()
    star_forming_regions.sorted_by_attribute("mass").reversed()

    # Generate a probability list of star forming region indices the
    # stars should associate to
    probabilities = (star_forming_regions.mass /
                     star_forming_regions.mass.sum())
    probabilities /= probabilities.sum()  # Ensure sum is exactly 1
    logger.info("Max & min probabilities: %s, %s", probabilities.max(),
                probabilities.min())

    #logger.info("All probabilities: %s", probabilities)

    # Create index list of star forming regions from probability list
    sample = numpy.random.choice(len(star_forming_regions),
                                 number_of_stars,
                                 p=probabilities)

    # Assign the stars to the sampled star forming regions
    star_forming_regions_sampled = star_forming_regions[sample]
    new_stars.position = star_forming_regions_sampled.position
    new_stars.velocity = star_forming_regions_sampled.velocity
    new_stars.origin_cloud = star_forming_regions_sampled.key
    new_stars.star_forming_radius = star_forming_regions_sampled.radius
    try:
        new_stars.star_forming_u = star_forming_regions_sampled.u
    except AttributeError:
        new_stars.star_forming_u = local_sound_speed**2

    # Random position of stars within the sink radius they assigned to
    rho = (numpy.random.random(number_of_stars) *
           new_stars.star_forming_radius)
    theta = (numpy.random.random(number_of_stars) * (2 * numpy.pi | units.rad))
    phi = (numpy.random.random(number_of_stars) * numpy.pi | units.rad)
    x = (rho * sin(phi) * cos(theta)).value_in(units.pc)
    y = (rho * sin(phi) * sin(theta)).value_in(units.pc)
    z = (rho * cos(phi)).value_in(units.pc)

    dX = list(zip(*[x, y, z])) | units.pc

    # Random velocity, sample magnitude from gaussian with local sound speed
    # like Wall et al (2019)
    # temperature = 10 | units.K

    # or (gamma * local_pressure / density).sqrt()
    velocity_magnitude = numpy.random.normal(
        # loc=0.0,  # <- since we already added the velocity of the sink
        scale=new_stars.star_forming_u.sqrt().value_in(units.kms),
        size=number_of_stars,
    ) | units.kms
    velocity_theta = (numpy.random.random(number_of_stars) *
                      (2 * numpy.pi | units.rad))
    velocity_phi = (numpy.random.random(number_of_stars) *
                    (numpy.pi | units.rad))
    vx = (velocity_magnitude * sin(velocity_phi) *
          cos(velocity_theta)).value_in(units.kms)
    vy = (velocity_magnitude * sin(velocity_phi) *
          sin(velocity_theta)).value_in(units.kms)
    vz = (velocity_magnitude * cos(velocity_phi)).value_in(units.kms)

    dV = list(zip(*[vx, vy, vz])) | units.kms

    #logger.info("Updating new stars...")
    new_stars.position += dX
    new_stars.velocity += dV

    # For Pentacle, this is the PP radius
    new_stars.radius = 0.05 | units.parsec

    # Remove sink mass according to the position of stars
    excess_star_mass = 0 | units.MSun
    for s in group:
        #logger.info('Sink mass before reduction: %s', s.mass.in_(units.MSun))
        total_star_mass_nearby = (
            new_stars[new_stars.origin_cloud == s.key]).total_mass()

        # To prevent sink mass becomes negative
        if s.mass > minimum_sink_mass:
            if (s.mass - total_star_mass_nearby) <= minimum_sink_mass:
                excess_star_mass += (total_star_mass_nearby - s.mass +
                                     minimum_sink_mass)
                #logger.info(
                #    'Sink mass goes below %s; excess mass is now %s',
                #    minimum_sink_mass.in_(units.MSun),
                #    excess_star_mass.in_(units.MSun)
                #)
                s.mass = minimum_sink_mass
            else:
                s.mass -= total_star_mass_nearby
        else:
            excess_star_mass += total_star_mass_nearby
            #logger.info(
            #    'Sink mass is already <= minimum mass allowed; '
            #    'excess mass is now %s',
            #    excess_star_mass.in_(units.MSun)
            #)

        #logger.info('Sink mass after reduction: %s', s.mass.in_(units.MSun))

    # Reduce all sinks in group equally with the excess star mass
    #logger.info('Reducing all sink mass equally with excess star mass...')
    mass_ratio = 1 - excess_star_mass / group.total_mass()
    group.mass *= mass_ratio

    logger.info("Total sink mass in group after sink mass reduction: %s",
                group.total_mass().in_(units.MSun))

    if shrink_sinks:
        group.radius = ((group.mass / group.initial_density) /
                        (4 / 3 * numpy.pi))**(1 / 3)
        #logger.info(
        #    "New radii: %s",
        #    group.radius.in_(units.pc)
        #)

    return new_stars
Пример #16
0
def coriolis_frequency(lat, omega=(1. | units.rev) / (sidereal_day)):
    return 2 * omega * sin(lat)