Beispiel #1
0
def test_reading_select_region_spatial(filename):
    """
    Tests reading select regions of the volume, comparing the masks attained with
    spatial_only = True and spatial_only = False.
    """

    full_data = load(filename)

    # Mask off the lower bottom corner of the volume.
    mask_region = mask(filename, spatial_only=True)
    mask_region_nospatial = mask(filename, spatial_only=False)

    restrict = array([
        [0.0, 0.0, 0.0] * full_data.metadata.boxsize.units,
        full_data.metadata.boxsize * 0.5,
    ]).T

    mask_region.constrain_spatial(restrict=restrict)
    mask_region_nospatial.constrain_spatial(restrict=restrict)

    selected_data = load(filename, mask=mask_region)
    selected_data_nospatial = load(filename, mask=mask_region_nospatial)

    selected_coordinates = selected_data.gas.coordinates
    selected_coordinates_nospatial = selected_data_nospatial.gas.coordinates

    assert (selected_coordinates_nospatial == selected_coordinates).all()

    return
Beispiel #2
0
def test_reading_select_region_half_box(filename):
    """
    Tests reading the spatial region and sees if it lies within the region
    we told it to!

    Specifically, we test to see if all particles lie within half a boxsize.
    """

    full_data = load(filename)

    # Mask off the lower bottom corner of the volume.
    mask_region = mask(filename, spatial_only=True)

    restrict = array([
        [0.0, 0.0, 0.0] * full_data.metadata.boxsize.units,
        full_data.metadata.boxsize * 0.49,
    ]).T

    mask_region.constrain_spatial(restrict=restrict)

    selected_data = load(filename, mask=mask_region)

    selected_coordinates = selected_data.gas.coordinates

    # Some of these particles will be outside because of the periodic BCs
    assert (
        (selected_coordinates / full_data.metadata.boxsize) > 0.5).sum() < 25
Beispiel #3
0
def process_single_halo(
        path_to_snap: str,
        path_to_catalogue: str
):
    # Read in halo properties
    with h5py.File(f'{path_to_catalogue}', 'r') as h5file:
        M500c = unyt.unyt_quantity(h5file['/SO_Mass_500_rhocrit'][0] * 1.e10, unyt.Solar_Mass)
        R500c = unyt.unyt_quantity(h5file['/SO_R_500_rhocrit'][0], unyt.Mpc)
        XPotMin = unyt.unyt_quantity(h5file['/Xcminpot'][0], unyt.Mpc)
        YPotMin = unyt.unyt_quantity(h5file['/Ycminpot'][0], unyt.Mpc)
        ZPotMin = unyt.unyt_quantity(h5file['/Zcminpot'][0], unyt.Mpc)

    # Read in gas particles to compute the core-excised temperature
    mask = sw.mask(f'{path_to_snap}', spatial_only=False)
    region = [[XPotMin - 0.5 * R500c, XPotMin + 0.5 * R500c],
              [YPotMin - 0.5 * R500c, YPotMin + 0.5 * R500c],
              [ZPotMin - 0.5 * R500c, ZPotMin + 0.5 * R500c]]
    mask.constrain_spatial(region)
    mask.constrain_mask(
        "gas", "temperatures",
        1.e5 * mask.units.temperature,
        1.e10 * mask.units.temperature
    )

    data = sw.load(f'{path_to_snap}', mask=mask)

    # Select hot gas within sphere and without core
    deltaX = data.gas.coordinates[:, 0] - XPotMin
    deltaY = data.gas.coordinates[:, 1] - YPotMin
    deltaZ = data.gas.coordinates[:, 2] - ZPotMin
    deltaR = np.sqrt(deltaX ** 2 + deltaY ** 2 + deltaZ ** 2)

    # Keep only particles inside 5 R500crit
    index = np.where((deltaR > 0.15 * R500c) & (deltaR < R500c))[0]
    data.gas.radial_distances = deltaR[index]
    data.gas.densities = data.gas.densities[index]
    data.gas.masses = data.gas.masses[index]
    data.gas.temperatures = data.gas.temperatures[index]

    data.gas.element_mass_fractions.hydrogen = data.gas.element_mass_fractions.hydrogen[index]
    data.gas.element_mass_fractions.helium = data.gas.element_mass_fractions.helium[index]
    data.gas.element_mass_fractions.carbon = data.gas.element_mass_fractions.carbon[index]
    data.gas.element_mass_fractions.nitrogen = data.gas.element_mass_fractions.nitrogen[index]
    data.gas.element_mass_fractions.oxygen = data.gas.element_mass_fractions.oxygen[index]
    data.gas.element_mass_fractions.neon = data.gas.element_mass_fractions.neon[index]
    data.gas.element_mass_fractions.magnesium = data.gas.element_mass_fractions.magnesium[index]
    data.gas.element_mass_fractions.silicon = data.gas.element_mass_fractions.silicon[index]
    data.gas.element_mass_fractions.iron = data.gas.element_mass_fractions.iron[index]

    Lx, Sx, Ypix = interpolate_xray(data)

    print(f"M_500_crit = {M500c:.3E}")
    print(f'LX = {np.sum(Lx):.3E}')

    return np.sum(Lx), Sx, Ypix
def process_single_halo(
        path_to_snap: str,
        path_to_catalogue: str
) -> Tuple[unyt.unyt_quantity]:
    # Read in halo properties
    with h5.File(f'{path_to_catalogue}', 'r') as h5file:
        XPotMin = unyt.unyt_quantity(h5file['/Xcminpot'][0], unyt.Mpc)
        YPotMin = unyt.unyt_quantity(h5file['/Ycminpot'][0], unyt.Mpc)
        ZPotMin = unyt.unyt_quantity(h5file['/Zcminpot'][0], unyt.Mpc)
        R500c = unyt.unyt_quantity(h5file['/SO_R_500_rhocrit'][0], unyt.Mpc)
        R200c = unyt.unyt_quantity(h5file['/R_200crit'][0], unyt.Mpc)
        M500c = unyt.unyt_quantity(
            h5file['/SO_Mass_500_rhocrit'][0] * 1.e10, unyt.Solar_Mass
        )
        Mbh_aperture50kpc = unyt.unyt_quantity(
            h5file['Aperture_SubgridMasses_aperture_total_bh_50_kpc'][0] * 1.e10, unyt.Solar_Mass
        )
        Mbh_max = unyt.unyt_quantity(
            h5file['/SubgridMasses_max_bh'][0] * 1.e10, unyt.Solar_Mass
        )
        Mstar_bcg_50kpc = unyt.unyt_quantity(
            h5file['/Aperture_mass_star_50_kpc'][0] * 1.e10, unyt.Solar_Mass
        )

        # Read in particles
        mask = sw.mask(f'{path_to_snap}', spatial_only=True)
        region = [[XPotMin - R200c, XPotMin + R200c],
                  [YPotMin - R200c, YPotMin + R200c],
                  [ZPotMin - R200c, ZPotMin + R200c]]
        mask.constrain_spatial(region)
        data = sw.load(f'{path_to_snap}', mask=mask)

        # Get positions for all BHs in the bounding region
        bh_positions = data.black_holes.coordinates
        bh_coordX = bh_positions[:, 0] - XPotMin
        bh_coordY = bh_positions[:, 1] - YPotMin
        bh_coordZ = bh_positions[:, 2] - ZPotMin
        bh_radial_distance = np.sqrt(bh_coordX ** 2 + bh_coordY ** 2 + bh_coordZ ** 2)

        # The central SMBH will probably be massive: filter above 1e8 Msun
        bh_masses = data.black_holes.subgrid_masses.to_physical()
        bh_top_massive_index = np.where(bh_masses.to('Msun').value > 1.e8)[0]

        massive_bh_radial_distances = bh_radial_distance[bh_top_massive_index]
        massive_bh_masses = bh_masses[bh_top_massive_index]

        # Get the central BH closest to centre of halo at z=0
        central_bh_index = np.argmin(bh_radial_distance[bh_top_massive_index])
        central_bh_dr = massive_bh_radial_distances[central_bh_index]
        central_bh_mass = massive_bh_masses[central_bh_index].to('Msun')

    return M500c, Mbh_aperture50kpc, Mbh_max, central_bh_mass, central_bh_dr, Mstar_bcg_50kpc
Beispiel #5
0
def test_subset_writer(filename):
    """
    Test to make sure subset writing works as intended

    Writes a subset of the cosmological volume to a snapshot file
    and compares result against masked load of the original file.
    """
    # Specify output filepath
    outfile = "subset_cosmological_volume.hdf5"

    # Create a mask
    mask = sw.mask(filename)

    boxsize = mask.metadata.boxsize

    # Decide which region we want to load
    load_region = [[0.25 * b, 0.75 * b] for b in boxsize]
    mask.constrain_spatial(load_region)

    # Write the subset
    write_subset(outfile, mask)

    # Compare subset of written subset of snapshot against corresponding region in 
    # full snapshot. This checks that both the metadata and dataset subsets are 
    # written properly.
    sub_mask = sw.mask(outfile)
    sub_load_region = [[0.375 * b, 0.625 * b] for b in boxsize]
    sub_mask.constrain_spatial(sub_load_region)
    mask.constrain_spatial(sub_load_region)

    snapshot = sw.load(filename, mask)
    sub_snapshot = sw.load(outfile, sub_mask)

    compare_data_contents(snapshot, sub_snapshot)

    # Clean up
    os.remove(outfile)

    return
Beispiel #6
0
def profile_3d_particles(
    path_to_snap: str,
    path_to_catalogue: str,
) -> tuple:
    # Read in halo properties
    vr_catalogue_handle = vr.load(path_to_catalogue)
    M500 = vr_catalogue_handle.spherical_overdensities.mass_500_rhocrit[0].to(
        'Msun')
    R500 = vr_catalogue_handle.spherical_overdensities.r_500_rhocrit[0].to(
        'Mpc')
    XPotMin = vr_catalogue_handle.positions.xcminpot[0].to('Mpc')
    YPotMin = vr_catalogue_handle.positions.ycminpot[0].to('Mpc')
    ZPotMin = vr_catalogue_handle.positions.zcminpot[0].to('Mpc')

    # Apply spatial mask to particles. SWIFTsimIO needs comoving coordinates
    # to filter particle coordinates, while VR outputs are in physical units.
    # Convert the region bounds to comoving, but keep the CoP and Rcrit in
    # physical units for later use.
    mask = sw.mask(path_to_snap, spatial_only=True)
    region = [[(XPotMin - R500), (XPotMin + R500)],
              [(YPotMin - R500), (YPotMin + R500)],
              [(ZPotMin - R500), (ZPotMin + R500)]]
    mask.constrain_spatial(region)
    data = sw.load(path_to_snap, mask=mask)

    # Select hot gas within sphere
    tempGas = data.gas.temperatures
    deltaX = data.gas.coordinates[:, 0] - XPotMin
    deltaY = data.gas.coordinates[:, 1] - YPotMin
    deltaZ = data.gas.coordinates[:, 2] - ZPotMin
    radial_distance = np.sqrt(deltaX**2 + deltaY**2 + deltaZ**2) / R500
    index = np.where((radial_distance < 2) & (tempGas > 1e5))[0]
    del tempGas, deltaX, deltaY, deltaZ

    mass_weighted_temperatures = (data.gas.temperatures *
                                  unyt.boltzmann_constant).to('keV')
    number_densities = (data.gas.densities.to('g/cm**3') /
                        (unyt.mp * mean_molecular_weight)).to('cm**-3')
    field_value = mass_weighted_temperatures / number_densities**(2 / 3)

    radial_distance = radial_distance[index]
    entropies = field_value[index]
    temperatures = mass_weighted_temperatures[index]

    rho_crit = unyt.unyt_quantity(
        data.metadata.cosmology.critical_density(data.metadata.z).value,
        'g/cm**3').to('Msun/Mpc**3')
    densities = data.gas.densities[index] / rho_crit

    return radial_distance, densities, temperatures, entropies, M500, R500
Beispiel #7
0
def process_single_halo(path_to_snap: str,
                        path_to_catalogue: str) -> Tuple[unyt.unyt_quantity]:
    # Read in halo properties
    with h5.File(f'{path_to_catalogue}', 'r') as h5file:
        M500c = unyt.unyt_quantity(h5file['/SO_Mass_500_rhocrit'][0] * 1.e10,
                                   unyt.Solar_Mass)
        R500c = unyt.unyt_quantity(h5file['/SO_R_500_rhocrit'][0], unyt.Mpc)
        Thot500c = unyt.unyt_quantity(
            h5file['/SO_T_gas_highT_1.000000_times_500.000000_rhocrit'][0],
            unyt.K)
        Zhot500c = unyt.unyt_quantity(
            h5file['/SO_Zmet_gas_highT_1.000000_times_500.000000_rhocrit'][0],
            unyt.solar_metallicity)
        XPotMin = unyt.unyt_quantity(h5file['/Xcminpot'][0], unyt.Mpc)
        YPotMin = unyt.unyt_quantity(h5file['/Ycminpot'][0], unyt.Mpc)
        ZPotMin = unyt.unyt_quantity(h5file['/Zcminpot'][0], unyt.Mpc)

    # Read in gas particles to compute the core-excised temperature
    mask = sw.mask(f'{path_to_snap}', spatial_only=False)
    region = [[XPotMin - R500c, XPotMin + R500c],
              [YPotMin - R500c, YPotMin + R500c],
              [ZPotMin - R500c, ZPotMin + R500c]]
    mask.constrain_spatial(region)
    mask.constrain_mask("gas", "temperatures",
                        Tcut_halogas * mask.units.temperature,
                        1.e12 * mask.units.temperature)
    data = sw.load(f'{path_to_snap}', mask=mask)
    posGas = data.gas.coordinates
    massGas = data.gas.masses
    mass_weighted_temperatures = data.gas.temperatures * data.gas.masses
    iron_fraction = data.gas.element_mass_fractions.iron * data.gas.masses

    # Select hot gas within sphere and without core
    deltaX = posGas[:, 0] - XPotMin
    deltaY = posGas[:, 1] - YPotMin
    deltaZ = posGas[:, 2] - ZPotMin
    deltaR = np.sqrt(deltaX**2 + deltaY**2 + deltaZ**2)

    index = np.where(deltaR < R500c)[0]
    iron_fraction_500c = np.sum(iron_fraction[index]) / np.sum(massGas[index])

    index = np.where((deltaR > 0.15 * R500c) & (deltaR < R500c))[0]
    Thot500c_nocore = np.sum(mass_weighted_temperatures[index]) / np.sum(
        massGas[index])

    # Convert temperatures to keV
    Thot500c = (Thot500c * unyt.boltzmann_constant).to('keV')
    Thot500c_nocore = (Thot500c_nocore * unyt.boltzmann_constant).to('keV')

    return M500c, Thot500c, Thot500c_nocore, Zhot500c, iron_fraction_500c
Beispiel #8
0
def test_region_mask_not_modified(filename):
    """
    Tests if a mask region is modified during the course of its use.

    Checks if https://github.com/SWIFTSIM/swiftsimio/issues/22 is broken.
    """

    this_mask = mask(filename, spatial_only=True)
    bs = this_mask.metadata.boxsize

    read = [[0 * b, 0.5 * b] for b in bs]
    read_constant = [[0 * b, 0.5 * b] for b in bs]

    this_mask._generate_cell_mask(read)

    assert read == read_constant
Beispiel #9
0
def process_single_halo(path_to_snap: str,
                        path_to_catalogue: str) -> Tuple[unyt.unyt_quantity]:
    # Read in halo properties
    with h5.File(f'{path_to_catalogue}', 'r') as h5file:
        M500c = unyt.unyt_quantity(h5file['/SO_Mass_500_rhocrit'][0] * 1.e10,
                                   unyt.Solar_Mass)
        R500c = unyt.unyt_quantity(h5file['/SO_R_500_rhocrit'][0], unyt.Mpc)
        Thot500c = unyt.unyt_quantity(
            h5file['/SO_T_gas_highT_1.000000_times_500.000000_rhocrit'][0],
            unyt.K)
        XPotMin = unyt.unyt_quantity(h5file['/Xcminpot'][0], unyt.Mpc)
        YPotMin = unyt.unyt_quantity(h5file['/Ycminpot'][0], unyt.Mpc)
        ZPotMin = unyt.unyt_quantity(h5file['/Zcminpot'][0], unyt.Mpc)

    # Read in gas particles to compute the core-excised temperature
    mask = sw.mask(f'{path_to_snap}', spatial_only=False)
    region = [[XPotMin - 5 * R500c, XPotMin + 5 * R500c],
              [YPotMin - 5 * R500c, YPotMin + 5 * R500c],
              [ZPotMin - 5 * R500c, ZPotMin + 5 * R500c]]
    mask.constrain_spatial(region)
    mask.constrain_mask("gas", "temperatures",
                        Tcut_halogas * mask.units.temperature,
                        1.e12 * mask.units.temperature)
    data = sw.load(f'{path_to_snap}', mask=mask)
    posGas = data.gas.coordinates
    massGas = data.gas.masses
    mass_weighted_temperatures = data.gas.temperatures * data.gas.masses

    # Select hot gas within sphere and without core
    deltaX = posGas[:, 0] - XPotMin
    deltaY = posGas[:, 1] - YPotMin
    deltaZ = posGas[:, 2] - ZPotMin
    deltaR = np.sqrt(deltaX**2 + deltaY**2 + deltaZ**2)

    index = np.where(deltaR < 5 * R500c)[0]
    compton_y = tsz_const * np.sum(
        mass_weighted_temperatures[index])  # / (np.pi * 25 * r500c ** 2)
    compton_y = compton_y.to('Mpc**2')

    index = np.where((deltaR > 0.15 * R500c) & (deltaR < R500c))[0]
    Thot500c_nocore = np.sum(mass_weighted_temperatures[index]) / np.sum(
        massGas[index])

    return M500c, Thot500c, Thot500c_nocore, compton_y
Beispiel #10
0
def test_dithered_cell_metadata_is_valid(filename):
    """
    Test that the metadata does what we think it does, in the
    dithered case.

    I.e. that it sets the particles contained in a top-level cell.
    """

    mask_region = mask(filename)
    # Because we sort by offset if we are using the metadata we
    # must re-order the data to be in the correct order
    spatial_constraint = mask_region.constrain_spatial(
        [[0 * b, b] for b in mask_region.metadata.boxsize])
    data = load(filename, mask=mask_region)

    cell_size = mask_region.cell_size.to(data.dark_matter.coordinates.units)
    boxsize = mask_region.metadata.boxsize[0].to(
        data.dark_matter.coordinates.units)
    offsets = mask_region.offsets["dark_matter"]
    counts = mask_region.counts["dark_matter"]

    start_offset = offsets
    stop_offset = offsets + counts

    for center, start, stop in zip(
            mask_region.centers.to(data.dark_matter.coordinates.units),
            start_offset,
            stop_offset,
    ):
        for dimension in range(0, 3):
            lower = (center - 0.5 * cell_size)[dimension]
            upper = (center + 0.5 * cell_size)[dimension]

            max = data.dark_matter.coordinates[start:stop, dimension].max()
            min = data.dark_matter.coordinates[start:stop, dimension].min()

            # Ignore things close to the boxsize
            if min < 0.05 * boxsize or max > 0.95 * boxsize:
                continue

            # Give it a little wiggle room
            assert max <= upper * 1.05
            assert min > lower * 0.95
Beispiel #11
0
def test_reading_select_region_metadata_not_spatial_only(filename):
    """
    The same as test_reading_select_region_metadata but for spatial_only=False.
    """

    full_data = load(filename)

    # Mask off the centre of the volume.
    mask_region = mask(filename, spatial_only=False)

    restrict = array(
        [full_data.metadata.boxsize * 0.26,
         full_data.metadata.boxsize * 0.74]).T

    mask_region.constrain_spatial(restrict=restrict)

    selected_data = load(filename, mask=mask_region)

    selected_coordinates = selected_data.gas.coordinates

    # Now need to repeat the selection by hand:
    subset_mask = logical_and.reduce([
        logical_and(x > y_lower, x < y_upper)
        for x, (y_lower, y_upper) in zip(full_data.gas.coordinates.T, restrict)
    ])

    # We also need to repeat for the thing we just selected; the cells only give
    # us an _approximate_ selection!
    selected_subset_mask = logical_and.reduce([
        logical_and(x > y_lower, x < y_upper)
        for x, (y_lower,
                y_upper) in zip(selected_data.gas.coordinates.T, restrict)
    ])

    hand_selected_coordinates = full_data.gas.coordinates[subset_mask]

    assert (hand_selected_coordinates ==
            selected_coordinates[selected_subset_mask]).all()

    return
Beispiel #12
0
def load_snapshot(snapshot_time, ax_lim):
    """ Select and load the particles to plot. """
    # Snapshot to load
    snapshot = "earth_impact_%06d.hdf5" % snapshot_time

    # Only load data with the axis limits and below z=0
    ax_lim = 0.1
    mask = sw.mask(snapshot)
    box_mid = 0.5 * mask.metadata.boxsize[0].to(unyt.Rearth)
    x_min = box_mid - ax_lim * unyt.Rearth
    x_max = box_mid + ax_lim * unyt.Rearth
    load_region = [[x_min, x_max], [x_min, x_max], [x_min, box_mid]]
    mask.constrain_spatial(load_region)

    # Load
    data = sw.load(snapshot, mask=mask)
    pos = data.gas.coordinates.to(unyt.Rearth) - box_mid
    id = data.gas.particle_ids
    mat_id = data.gas.material_ids.value

    # Restrict to z < 0
    sel = np.where(pos[:, 2] < 0)[0]
    pos = pos[sel]
    id = id[sel]
    mat_id = mat_id[sel]

    # Sort in z order so higher particles are plotted on top
    sort = np.argsort(pos[:, 2])
    pos = pos[sort]
    id = id[sort]
    mat_id = mat_id[sort]

    # Edit material IDs for particles in the impactor
    num_in_target = 99740
    mat_id[num_in_target <= id] += id_body

    return pos, mat_id
Beispiel #13
0
def dm_map_parent(
        run_name: str,
        velociraptor_properties_parent: str,
        snap_filepath_parent: str,
        velociraptor_properties_zoom: str,
        out_to_radius: Tuple[int, str] = (5, 'r200c'),
        highres_radius: Tuple[int, str] = (6, 'r500c'),
        output_directory: str = '.'
) -> None:
    print(f"Rendering {snap_filepath_parent}...")

    # Rendezvous over parent VR catalogue using zoom information
    with h5py.File(velociraptor_properties_zoom, 'r') as vr_file:
        idx, M200c, R200c, Xcminpot, Ycminpot, Zcminpot = find_object(
            vr_properties_catalog=velociraptor_properties_parent,
            sample_structType=10,
            sample_mass_lower_lim=vr_file['/Mass_200crit'][0] * 1e10 * 0.8,
            sample_x=vr_file['/Xcminpot'][0],
            sample_y=vr_file['/Ycminpot'][0],
            sample_z=vr_file['/Zcminpot'][0],
        )
    with h5py.File(velociraptor_properties_parent, 'r') as vr_file:
        R500c = unyt.unyt_quantity(vr_file['/SO_R_500_rhocrit'][idx], unyt.Mpc)

    M200c = unyt.unyt_quantity(M200c, unyt.Solar_Mass)
    R200c = unyt.unyt_quantity(R200c, unyt.Mpc)
    xCen = unyt.unyt_quantity(Xcminpot, unyt.Mpc)
    yCen = unyt.unyt_quantity(Ycminpot, unyt.Mpc)
    zCen = unyt.unyt_quantity(Zcminpot, unyt.Mpc)

    if out_to_radius[1] == 'r200c':
        size = out_to_radius[0] * R200c
    elif out_to_radius[1] == 'r500c':
        size = out_to_radius[0] * R500c
    elif out_to_radius[1] == 'Mpc' or out_to_radius[1] is None:
        size = unyt.unyt_quantity(out_to_radius[0], unyt.Mpc)

    if highres_radius[1] == 'r200c':
        _highres_radius = highres_radius[0] * R200c
    elif highres_radius[1] == 'r500c':
        _highres_radius = highres_radius[0] * R500c
    elif highres_radius[1] == 'Mpc' or highres_radius[1] is None:
        _highres_radius = unyt.unyt_quantity(highres_radius[0], unyt.Mpc)

    # Construct spatial mask to feed into swiftsimio
    mask = sw.mask(snap_filepath_parent)
    region = [
        [xCen - size, xCen + size],
        [yCen - size, yCen + size],
        [zCen - size, zCen + size]
    ]
    mask.constrain_spatial(region)
    data = sw.load(snap_filepath_parent, mask=mask)

    # Generate smoothing lengths for the dark matter
    data.dark_matter.smoothing_lengths = generate_smoothing_lengths(
        data.dark_matter.coordinates,
        data.metadata.boxsize,
        kernel_gamma=1.8,
        neighbours=57,
        speedup_fac=2,
        dimension=3,
    )

    # data.dark_matter.coordinates[:, 0] = wrap(
    #     data.dark_matter.coordinates[:, 0] - xCen,
    #     data.metadata.boxsize[0]
    # )
    # data.dark_matter.coordinates[:, 1] = wrap(
    #     data.dark_matter.coordinates[:, 1] - yCen,
    #     data.metadata.boxsize[1]
    # )
    # data.dark_matter.coordinates[:, 2] = wrap(
    #     data.dark_matter.coordinates[:, 2] - zCen,
    #     data.metadata.boxsize[2]
    # )
    
    dm_mass = dm_render(data, region=(
        [
            xCen - size,
            xCen + size,
            yCen - size,
            yCen + size
        ]
    ), resolution=resolution)

    # Make figure
    fig, ax = plt.subplots(figsize=(8, 8), dpi=resolution // 8)
    fig.subplots_adjust(0, 0, 1, 1)
    ax.axis("off")
    ax.imshow(dm_mass.T, norm=LogNorm(), cmap="inferno", origin="lower", extent=(region[0] + region[1]))
    info = ax.text(
        0.025,
        0.025,
        (
            f"Halo {run_name:s} DMO - parent\n"
            f"$z={data.metadata.z:3.3f}$\n"
            f"$M_{{200c}}={latex_float(M200c.value)}\\ {M200c.units.latex_repr}$\n"
            f"$R_{{200c}}={latex_float(R200c.value)}\\ {R200c.units.latex_repr}$\n"
            f"$R_\\mathrm{{clean}}={highres_radius[0]}\\ {highres_radius[1]}$"
        ),
        color="white",
        ha="left",
        va="bottom",
        alpha=0.8,
        transform=ax.transAxes,
    )
    info.set_bbox(dict(facecolor='black', alpha=0.2, edgecolor='grey'))
    ax.text(
        xCen,
        yCen + 1.05 * R200c,
        r"$R_{200c}$",
        color="black",
        ha="center",
        va="bottom"
    )
    ax.text(
        xCen,
        yCen + 1.02 * _highres_radius,
        r"$R_\mathrm{clean}$",
        color="white",
        ha="center",
        va="bottom"
    )
    circle_r200 = plt.Circle((xCen, yCen), R200c, color="black", fill=False, linestyle='-')
    circle_clean = plt.Circle((xCen, yCen), _highres_radius.value, color="white", fill=False, linestyle=':')
    ax.add_artist(circle_r200)
    ax.add_artist(circle_clean)
    ax.set_xlim([xCen.value - size.value, xCen.value + size.value])
    ax.set_ylim([yCen.value - size.value, yCen.value + size.value])
    fig.savefig(f"{output_directory}/{run_name}_dark_matter_map_parent.png")
    plt.close(fig)
    print(f"Saved: {output_directory}/{run_name}_dark_matter_map_parent.png")
    del data, dm_mass
    plt.close('all')

    return
def contamination_map(run_name: str,
                      velociraptor_properties_zoom: str,
                      snap_filepath_zoom: str,
                      out_to_radius: Tuple[int, str] = (5, 'r200c'),
                      highres_radius: Tuple[int, str] = (6, 'r500c'),
                      output_directory: str = '.') -> None:
    # Rendezvous over parent VR catalogue using zoom information
    with h5py.File(velociraptor_properties_zoom, 'r') as vr_file:
        M200c = unyt.unyt_quantity(vr_file['/Mass_200crit'][0] * 1e10,
                                   unyt.Solar_Mass)
        R200c = unyt.unyt_quantity(vr_file['/R_200crit'][0], unyt.Mpc)
        R500c = unyt.unyt_quantity(vr_file['/SO_R_500_rhocrit'][0], unyt.Mpc)
        xCen = unyt.unyt_quantity(vr_file['/Xcminpot'][0], unyt.Mpc)
        yCen = unyt.unyt_quantity(vr_file['/Ycminpot'][0], unyt.Mpc)
        zCen = unyt.unyt_quantity(vr_file['/Zcminpot'][0], unyt.Mpc)

    # EAGLE-XL data path
    print(f"Rendering {snap_filepath_zoom}...")

    if out_to_radius[1] == 'r200c':
        size = out_to_radius[0] * R200c
    elif out_to_radius[1] == 'r500c':
        size = out_to_radius[0] * R500c
    elif out_to_radius[1] == 'Mpc' or out_to_radius[1] is None:
        size = unyt.unyt_quantity(out_to_radius[0], unyt.Mpc)
    else:
        raise ValueError(
            "The `out_to_radius` input is not in the correct format or not recognised."
        )

    if highres_radius[1] == 'r200c':
        _highres_radius = highres_radius[0] * R200c
    elif highres_radius[1] == 'r500c':
        _highres_radius = highres_radius[0] * R500c
    elif highres_radius[1] == 'Mpc' or highres_radius[1] is None:
        _highres_radius = unyt.unyt_quantity(highres_radius[0], unyt.Mpc)
    else:
        raise ValueError(
            "The `highres_radius` input is not in the correct format or not recognised."
        )

    mask = sw.mask(snap_filepath_zoom)
    region = [[xCen - size, xCen + size], [yCen - size, yCen + size],
              [zCen - size, zCen + size]]
    mask.constrain_spatial(region)

    # Load data using mask
    data = sw.load(snap_filepath_zoom, mask=mask)
    posDM = data.dark_matter.coordinates / data.metadata.a
    highres_coordinates = {
        'x':
        wrap(posDM[:, 0] - xCen, data.metadata.boxsize[0]),
        'y':
        wrap(posDM[:, 1] - yCen, data.metadata.boxsize[1]),
        'z':
        wrap(posDM[:, 2] - zCen, data.metadata.boxsize[2]),
        'r':
        np.sqrt(
            wrap(posDM[:, 0] - xCen, data.metadata.boxsize[0])**2 +
            wrap(posDM[:, 1] - yCen, data.metadata.boxsize[1])**2 +
            wrap(posDM[:, 2] - zCen, data.metadata.boxsize[2])**2)
    }
    del posDM
    posDM = data.boundary.coordinates / data.metadata.a
    lowres_coordinates = {
        'x':
        wrap(posDM[:, 0] - xCen, data.metadata.boxsize[0]),
        'y':
        wrap(posDM[:, 1] - yCen, data.metadata.boxsize[1]),
        'z':
        wrap(posDM[:, 2] - zCen, data.metadata.boxsize[2]),
        'r':
        np.sqrt(
            wrap(posDM[:, 0] - xCen, data.metadata.boxsize[0])**2 +
            wrap(posDM[:, 1] - yCen, data.metadata.boxsize[1])**2 +
            wrap(posDM[:, 2] - zCen, data.metadata.boxsize[2])**2)
    }
    del posDM

    # Flag contamination particles within 5 R200
    contaminated_idx = np.where(lowres_coordinates['r'] < _highres_radius)[0]
    contaminated_r200_idx = np.where(lowres_coordinates['r'] < 1. * R200c)[0]
    print(
        f"Total low-res DM: {len(lowres_coordinates['r'])} particles detected")
    print(
        f"Contaminating low-res DM (< R_clean): {len(contaminated_idx)} particles detected"
    )
    print(
        f"Contaminating low-res DM (< r200c): {len(contaminated_r200_idx)} particles detected"
    )

    # Make particle maps
    fig, ax = plt.subplots(figsize=(7, 7), dpi=1024 // 7)

    ax.set_aspect('equal')
    ax.set_ylabel(r"$y$ [Mpc]")
    ax.set_xlabel(r"$x$ [Mpc]")

    ax.plot(highres_coordinates['x'][::4],
            highres_coordinates['y'][::4],
            ',',
            c="C0",
            alpha=0.1,
            label='Highres')
    ax.plot(lowres_coordinates['x'][contaminated_idx],
            lowres_coordinates['y'][contaminated_idx],
            'x',
            c="red",
            alpha=1,
            label='Lowres contaminating')
    ax.plot(lowres_coordinates['x'][~contaminated_idx],
            lowres_coordinates['y'][~contaminated_idx],
            '.',
            c="green",
            alpha=0.2,
            label='Lowres clean')

    ax.text(
        0.025,
        0.025,
        (f"Halo {run_name:s} DMO\n"
         f"$z={data.metadata.z:3.3f}$\n"
         f"$M_{{200c}}={latex_float(M200c.value)}$ M$_\odot$\n"
         f"$R_{{200c}}={latex_float(R200c.value)}$ Mpc\n"
         f"$R_{{\\rm clean}}={latex_float(_highres_radius.value)}$ Mpc"),
        color="black",
        ha="left",
        va="bottom",
        transform=ax.transAxes,
    )
    ax.text(0,
            0 + 1.05 * R200c,
            r"$R_{200c}$",
            color="black",
            ha="center",
            va="bottom")
    ax.text(0,
            0 + 1.002 * 5 * R200c,
            r"$5 \times R_{200c}$",
            color="grey",
            ha="center",
            va="bottom")
    ax.text(0,
            0 + 1.02 * _highres_radius,
            r"$R_\mathrm{clean}$",
            color="red",
            ha="center",
            va="bottom")
    circle_r200 = plt.Circle((0, 0),
                             R200c,
                             color="black",
                             fill=False,
                             linestyle='-')
    circle_5r200 = plt.Circle((0, 0),
                              5 * R200c,
                              color="grey",
                              fill=False,
                              linestyle='--')
    circle_clean = plt.Circle((0, 0),
                              _highres_radius.value,
                              color="red",
                              fill=False,
                              linestyle=':')
    ax.add_artist(circle_r200)
    ax.add_artist(circle_5r200)
    ax.add_artist(circle_clean)
    ax.set_xlim([-size.value, size.value])
    ax.set_ylim([-size.value, size.value])
    plt.legend()
    fig.savefig(
        f"{output_directory}/{run_name}_contamination_map{out_to_radius[0]}{out_to_radius[1]}.png"
    )
    print(
        f"Saved: {output_directory}/{run_name}_contamination_map{out_to_radius[0]}{out_to_radius[1]}.png"
    )
    plt.close(fig)
def contamination_radial_histogram(run_name: str,
                                   velociraptor_properties_zoom: str,
                                   snap_filepath_zoom: str,
                                   out_to_radius: Tuple[int,
                                                        str] = (5, 'r200c'),
                                   highres_radius: Tuple[int,
                                                         str] = (6, 'r500c'),
                                   output_directory: str = '.') -> None:
    # Rendezvous over parent VR catalogue using zoom information
    with h5py.File(velociraptor_properties_zoom, 'r') as vr_file:
        M200c = unyt.unyt_quantity(vr_file['/Mass_200crit'][0] * 1e10,
                                   unyt.Solar_Mass)
        R200c = unyt.unyt_quantity(vr_file['/R_200crit'][0], unyt.Mpc)
        R500c = unyt.unyt_quantity(vr_file['/SO_R_500_rhocrit'][0], unyt.Mpc)
        xCen = unyt.unyt_quantity(vr_file['/Xcminpot'][0], unyt.Mpc)
        yCen = unyt.unyt_quantity(vr_file['/Ycminpot'][0], unyt.Mpc)
        zCen = unyt.unyt_quantity(vr_file['/Zcminpot'][0], unyt.Mpc)

    # EAGLE-XL data path
    print(f"Rendering {snap_filepath_zoom}...")

    if out_to_radius[1] == 'r200c':
        size = out_to_radius[0] * R200c
    elif out_to_radius[1] == 'r500c':
        size = out_to_radius[0] * R500c
    elif out_to_radius[1] == 'Mpc' or out_to_radius[1] is None:
        size = unyt.unyt_quantity(out_to_radius[0], unyt.Mpc)

    if highres_radius[1] == 'r200c':
        _highres_radius = highres_radius[0] * R200c
    elif highres_radius[1] == 'r500c':
        _highres_radius = highres_radius[0] * R500c
    elif highres_radius[1] == 'Mpc' or highres_radius[1] is None:
        _highres_radius = unyt.unyt_quantity(highres_radius[0], unyt.Mpc)

    mask = sw.mask(snap_filepath_zoom)
    region = [[xCen - size, xCen + size], [yCen - size, yCen + size],
              [zCen - size, zCen + size]]
    mask.constrain_spatial(region)

    # Load data using mask
    data = sw.load(snap_filepath_zoom, mask=mask)
    posDM = data.dark_matter.coordinates / data.metadata.a
    highres_coordinates = {
        'x':
        wrap(posDM[:, 0] - xCen, data.metadata.boxsize[0]),
        'y':
        wrap(posDM[:, 1] - yCen, data.metadata.boxsize[1]),
        'z':
        wrap(posDM[:, 2] - zCen, data.metadata.boxsize[2]),
        'r':
        np.sqrt(
            wrap(posDM[:, 0] - xCen, data.metadata.boxsize[0])**2 +
            wrap(posDM[:, 1] - yCen, data.metadata.boxsize[1])**2 +
            wrap(posDM[:, 2] - zCen, data.metadata.boxsize[2])**2)
    }
    del posDM
    posDM = data.boundary.coordinates / data.metadata.a
    lowres_coordinates = {
        'x':
        wrap(posDM[:, 0] - xCen, data.metadata.boxsize[0]),
        'y':
        wrap(posDM[:, 1] - yCen, data.metadata.boxsize[1]),
        'z':
        wrap(posDM[:, 2] - zCen, data.metadata.boxsize[2]),
        'r':
        np.sqrt(
            wrap(posDM[:, 0] - xCen, data.metadata.boxsize[0])**2 +
            wrap(posDM[:, 1] - yCen, data.metadata.boxsize[1])**2 +
            wrap(posDM[:, 2] - zCen, data.metadata.boxsize[2])**2)
    }
    del posDM

    # Histograms
    contaminated_idx = np.where(lowres_coordinates['r'] < _highres_radius)[0]
    bins = np.linspace(0, size, 40)
    hist, bin_edges = np.histogram(lowres_coordinates['r'][contaminated_idx],
                                   bins=bins)
    lowres_coordinates['r_bins'] = (bin_edges[1:] + bin_edges[:-1]) / 2 / R200c
    lowres_coordinates['hist_contaminating'] = hist
    del hist, bin_edges
    hist, _ = np.histogram(lowres_coordinates['r'], bins=bins)
    lowres_coordinates['hist_all'] = hist
    del hist
    hist, _ = np.histogram(highres_coordinates['r'], bins=bins)
    highres_coordinates['r_bins'] = lowres_coordinates['r_bins']
    highres_coordinates['hist_all'] = hist
    del bins, hist

    # Make radial distribution plot
    fig, ax = plt.subplots()

    ax.set_yscale('log')
    ax.set_ylabel("Number of particles")
    ax.set_xlabel(r"$R\ /\ R_{200c}$")

    ax.step(highres_coordinates['r_bins'],
            highres_coordinates['hist_all'],
            where='mid',
            color='grey',
            label='Highres all')
    ax.step(lowres_coordinates['r_bins'],
            lowres_coordinates['hist_all'],
            where='mid',
            color='green',
            label='Lowres all')
    ax.step(lowres_coordinates['r_bins'],
            lowres_coordinates['hist_contaminating'],
            where='mid',
            color='red',
            label='Lowres contaminating')
    ax.text(
        0.025,
        0.025,
        (f"Halo {run_name:s} DMO\n"
         f"$z={data.metadata.z:3.3f}$\n"
         f"$M_{{200c}}={latex_float(M200c.value)}$ M$_\odot$\n"
         f"$R_{{200c}}={latex_float(R200c.value)}$ Mpc\n"
         f"$R_{{\\rm clean}}={latex_float(_highres_radius.value)}$ Mpc"),
        color="black",
        ha="left",
        va="bottom",
        transform=ax.transAxes,
    )

    ax.axvline(1, color="grey", linestyle='--')
    ax.axvline(_highres_radius / R200c, color="red", linestyle='--')
    ax.set_xlim([0, size.value])
    plt.legend()
    fig.tight_layout()
    fig.savefig(
        f"{output_directory}/{run_name}_contamination_hist{out_to_radius[0]}{out_to_radius[1]}.png"
    )
    print(
        f"Saved: {output_directory}/{run_name}_contamination_hist{out_to_radius[0]}{out_to_radius[1]}.png"
    )
    plt.close(fig)
Beispiel #16
0
def cumulative_mass_compare_plot(
        run_name: str,
        snap_filepath_parent: str = None,
        snap_filepath_zoom: List[str] = None,
        velociraptor_properties_zoom: List[str] = None,
        output_directory: str = None) -> None:
    """
    This function compares the cumulative mass of DMO zooms to their
    corresponding parent halo. It also allows to assess numerical
    convergence by overlapping multiple profiles from zooms with different
    resolutions. The cumulative mass profiles are then listed in the legend,
    where the DM particle mass is quoted.
    The zoom inputs are in the form of arrays of strings, to allow
    for multiple entries. Each entry is a zoom snap/VR output resolution.
    The function allows not to plot either the parent mass profile
    or the zooms. At least one of them must be entered.

    :param run_name: str
        A custom and identifiable name for the run. Currently the standard
        name follows the scheme {AUTHOR_INITIALS}{HALO_ID}, e.g. SK0 or EA1.
        This argument must be defined.
    :param snap_filepath_parent: str
        The complete path to the snapshot of the parent box.
        If parameter is None, the mass profile of the parent box is not
        displayed.
    :param snap_filepath_zoom: list(str)
        The list of complete paths to the snapshots of the zooms
        at different resolution. Note: the order must match that of
        the `velociraptor_properties_zoom` parameter. If parameter
        is None, the mass profile of the zoom is not displayed and the
        `velociraptor_properties_zoom` parameter is ignored.
    :param velociraptor_properties_zoom: list(str)
        The list of complete paths to the VR outputs (properties) of the
        zooms at different resolution. Note: the order must match that of
        the `snap_filepath_zoom` parameter. If `snap_filepath_zoom` is None,
        then this parameter is ignored. If this parameter is None and
        `snap_filepath_zoom` is defined, raises an error.
    :param output_directory: str
        The output directory where to save the plot. This code assumes
        that the output directory exists. If it does not exist, matplotlib
        will return an error. This argument must be defined.
    :return: None
    """

    # ARGS CHECK #
    assert run_name
    assert snap_filepath_parent or snap_filepath_zoom
    if snap_filepath_zoom and velociraptor_properties_zoom:
        assert len(snap_filepath_zoom) == len(velociraptor_properties_zoom)
    elif not snap_filepath_zoom:
        velociraptor_properties_zoom = None
    elif snap_filepath_zoom and not velociraptor_properties_zoom:
        raise ValueError
    assert output_directory

    # TEMPORARY #
    # Split the run_name into author and halo_id to make everything work fine for now
    import re
    match = re.match(r"([a-z]+)([0-9]+)", run_name, re.I)
    author = None
    halo_id = None
    if match:
        author, halo_id = match.groups()
    halo_id = int(halo_id)

    fig, (ax,
          ax_residual) = plt.subplots(nrows=2,
                                      ncols=1,
                                      figsize=(3.5, 4.1),
                                      sharex=True,
                                      gridspec_kw={'height_ratios': [3, 1]})

    # PARENT #
    if snap_filepath_parent:
        # Load VR output gathered from the halo selection process
        lines = np.loadtxt(f"{output_directory}/halo_selected_{author}.txt",
                           comments="#",
                           delimiter=",",
                           unpack=False).T
        M200c = lines[1] * 1e13
        R200c = lines[2]
        Xcminpot = lines[3]
        Ycminpot = lines[4]
        Zcminpot = lines[5]
        M200c = unyt.unyt_quantity(M200c[halo_id], unyt.Solar_Mass)
        R200c = unyt.unyt_quantity(R200c[halo_id], unyt.Mpc)
        xCen = unyt.unyt_quantity(Xcminpot[halo_id], unyt.Mpc)
        yCen = unyt.unyt_quantity(Ycminpot[halo_id], unyt.Mpc)
        zCen = unyt.unyt_quantity(Zcminpot[halo_id], unyt.Mpc)

        # Construct spatial mask to feed into swiftsimio
        size = radius_bounds[1] * R200c
        mask = sw.mask(snap_filepath_parent)
        region = [[xCen - size, xCen + size], [yCen - size, yCen + size],
                  [zCen - size, zCen + size]]
        mask.constrain_spatial(region)
        data = sw.load(snap_filepath_parent, mask=mask)

        # Get DM particle coordinates and compute radial distance from CoP in R200 units
        posDM = data.dark_matter.coordinates / data.metadata.a
        r = np.sqrt((posDM[:, 0] - xCen)**2 + (posDM[:, 1] - yCen)**2 +
                    (posDM[:, 2] - zCen)**2)

        # Calculate particle mass and rho_crit
        unitLength = data.metadata.units.length
        unitMass = data.metadata.units.mass
        rho_crit = unyt.unyt_quantity(
            data.metadata.cosmology['Critical density [internal units]'],
            unitMass / unitLength**3)[0].to('Msun/Mpc**3')
        rhoMean = rho_crit * data.metadata.cosmology['Omega_m']
        vol = data.metadata.boxsize[0]**3
        numPart = data.metadata.n_dark_matter
        particleMass = rhoMean * vol / numPart
        parent_mass_resolution = particleMass
        particleMasses = np.ones_like(r.value) * particleMass

        # Construct bins and compute density profile
        lbins = np.logspace(np.log10(radius_bounds[0]),
                            np.log10(radius_bounds[1]), bins)
        r_scaled = r / R200c
        hist, bin_edges = np.histogram(r_scaled,
                                       bins=lbins,
                                       weights=particleMasses)
        bin_centre = np.sqrt(bin_edges[1:] * bin_edges[:-1])
        cumulative_mass_parent = np.cumsum(hist)

        # Plot density profile for each selected halo in volume
        cumulative_mass_parent[cumulative_mass_parent == 0] = np.nan
        parent_label = f'Parent: $m_\\mathrm{{DM}} = {latex_float(parent_mass_resolution.value[0])}\\ {parent_mass_resolution.units.latex_repr}$'
        ax.plot(bin_centre,
                cumulative_mass_parent,
                c="grey",
                linestyle="-",
                label=parent_label)

        # Compute convergence radius
        conv_radius = convergence_radius(r, particleMasses, rho_crit) / R200c
        ax.axvline(conv_radius, color='grey', linestyle='--')
        ax_residual.axvline(conv_radius, color='grey', linestyle='--')
        t = ax.text(conv_radius,
                    ax.get_ylim()[1],
                    'Convergence radius',
                    ha='center',
                    va='top',
                    rotation='vertical',
                    alpha=0.6)
        t.set_bbox(dict(facecolor='white', alpha=0.6, edgecolor='none'))

    # ZOOMS #
    if snap_filepath_zoom:

        # Set-up colors
        cmap_discrete = plt.cm.get_cmap(cmap_name,
                                        len(velociraptor_properties_zoom) + 3)
        cmaplist = [cmap_discrete(i) for i in range(cmap_discrete.N)]

        for snap_path, vrprop_path, color in zip(snap_filepath_zoom,
                                                 velociraptor_properties_zoom,
                                                 cmaplist):
            # Load velociraptor data
            with h5py.File(vrprop_path, 'r') as vr_file:
                M200c = vr_file['/Mass_200crit'][0] * 1e10
                R200c = vr_file['/R_200crit'][0]
                Xcminpot = vr_file['/Xcminpot'][0]
                Ycminpot = vr_file['/Ycminpot'][0]
                Zcminpot = vr_file['/Zcminpot'][0]

            M200c = unyt.unyt_quantity(M200c, unyt.Solar_Mass)
            R200c = unyt.unyt_quantity(R200c, unyt.Mpc)
            xCen = unyt.unyt_quantity(Xcminpot, unyt.Mpc)
            yCen = unyt.unyt_quantity(Ycminpot, unyt.Mpc)
            zCen = unyt.unyt_quantity(Zcminpot, unyt.Mpc)

            # Construct spatial mask to feed into swiftsimio
            size = radius_bounds[1] * R200c
            mask = sw.mask(snap_path)
            region = [[xCen - size, xCen + size], [yCen - size, yCen + size],
                      [zCen - size, zCen + size]]
            mask.constrain_spatial(region)
            data = sw.load(snap_path, mask=mask)

            # Get DM particle coordinates and compute radial distance from CoP in R200 units
            posDM = data.dark_matter.coordinates / data.metadata.a
            r = np.sqrt((posDM[:, 0] - xCen)**2 + (posDM[:, 1] - yCen)**2 +
                        (posDM[:, 2] - zCen)**2)

            # Calculate particle mass and rho_crit
            unitLength = data.metadata.units.length
            unitMass = data.metadata.units.mass
            rho_crit = unyt.unyt_quantity(
                data.metadata.cosmology['Critical density [internal units]'],
                unitMass / unitLength**3)[0].to('Msun/Mpc**3')
            particleMasses = data.dark_matter.masses.to('Msun')
            zoom_mass_resolution = particleMasses

            # Construct bins and compute density profile
            lbins = np.logspace(np.log10(radius_bounds[0]),
                                np.log10(radius_bounds[1]), bins)
            r_scaled = r / R200c
            hist, bin_edges = np.histogram(r_scaled,
                                           bins=lbins,
                                           weights=particleMasses)
            bin_centre = np.sqrt(bin_edges[1:] * bin_edges[:-1])
            cumulative_mass_zoom = np.cumsum(hist)

            # Plot density profile for each selected halo in volume
            cumulative_mass_zoom[cumulative_mass_zoom == 0] = np.nan
            zoom_label = f'Zoom: $m_\\mathrm{{DM}} = {latex_float(zoom_mass_resolution.value[0])}\\ {zoom_mass_resolution.units.latex_repr}$'
            ax.plot(bin_centre,
                    cumulative_mass_zoom,
                    c=color,
                    linestyle="-",
                    label=zoom_label)

            # Compute convergence radius
            conv_radius = convergence_radius(r, particleMasses,
                                             rho_crit) / R200c
            ax.axvline(conv_radius, color=color, linestyle='--')
            t = ax.text(conv_radius,
                        ax.get_ylim()[1],
                        'Convergence radius',
                        ha='center',
                        va='top',
                        rotation='vertical',
                        alpha=0.6)
            t.set_bbox(dict(facecolor='white', alpha=0.6, edgecolor='none'))

            # RESIDUALS #
            if snap_filepath_parent and snap_filepath_zoom:
                residual = (cumulative_mass_zoom -
                            cumulative_mass_parent) / cumulative_mass_parent
                ax_residual.axhline(0, color='grey', linestyle='-')
                ax_residual.plot(bin_centre, residual, c=color, linestyle="-")
                ax_residual.axvline(conv_radius, color=color, linestyle='--')

    ax.text(
        0.975,
        0.025,
        (f"Halo {run_name:s} DMO\n"
         f"$z={data.metadata.z:3.3f}$\n"
         "Zoom VR output:\n"
         f"$M_{{200c}}={latex_float(M200c.value)}\\ {M200c.units.latex_repr}$\n"
         f"$R_{{200c}}={latex_float(R200c.value)}\\ {R200c.units.latex_repr}$"
         ),
        color="black",
        ha="right",
        va="bottom",
        backgroundcolor='white',
        transform=ax.transAxes,
    )

    ax.axvline(1, color="grey", linestyle='--')
    ax_residual.axvline(1, color="grey", linestyle='--')
    ax_residual.set_xlim(radius_bounds[0], radius_bounds[1])
    ax_residual.set_ylim(residual_bounds[0], residual_bounds[1])
    ax.set_xscale('log')
    ax.set_yscale('log')
    ax.set_ylabel(f"$M_{{\\rm DM}} (< R)\\ [{M200c.units.latex_repr}]$")
    ax_residual.set_ylabel(f"$\\Delta M\\ /\\ M_{{\\rm parent}}\\ (< R)$")
    ax_residual.set_xlabel(r"$R\ /\ R_{200c}$")
    ax.legend(loc="upper right")
    fig.tight_layout()
    fig.savefig(f"{output_directory}/{run_name}_cumulative_mass_compare.png")
    plt.close(fig)

    return
Beispiel #17
0
def profile_3d_shells(
    path_to_snap: str,
    path_to_catalogue: str,
) -> tuple:
    # Read in halo properties
    vr_catalogue_handle = vr.load(path_to_catalogue)
    M500 = vr_catalogue_handle.spherical_overdensities.mass_500_rhocrit[0].to(
        'Msun')
    R500 = vr_catalogue_handle.spherical_overdensities.r_500_rhocrit[0].to(
        'Mpc')
    XPotMin = vr_catalogue_handle.positions.xcminpot[0].to('Mpc')
    YPotMin = vr_catalogue_handle.positions.ycminpot[0].to('Mpc')
    ZPotMin = vr_catalogue_handle.positions.zcminpot[0].to('Mpc')

    # Apply spatial mask to particles. SWIFTsimIO needs comoving coordinates
    # to filter particle coordinates, while VR outputs are in physical units.
    # Convert the region bounds to comoving, but keep the CoP and Rcrit in
    # physical units for later use.
    mask = sw.mask(path_to_snap, spatial_only=True)
    region = [[(XPotMin - R500), (XPotMin + R500)],
              [(YPotMin - R500), (YPotMin + R500)],
              [(ZPotMin - R500), (ZPotMin + R500)]]
    mask.constrain_spatial(region)
    data = sw.load(path_to_snap, mask=mask)

    # Select gas within sphere and main FOF halo
    fof_id = data.gas.fofgroup_ids
    tempGas = data.gas.temperatures
    deltaX = data.gas.coordinates[:, 0] - XPotMin
    deltaY = data.gas.coordinates[:, 1] - YPotMin
    deltaZ = data.gas.coordinates[:, 2] - ZPotMin
    radial_distance = np.sqrt(deltaX**2 + deltaY**2 + deltaZ**2) / R500
    index = np.where((radial_distance < 2) & (fof_id == 1)
                     & (tempGas > 1e5))[0]
    del deltaX, deltaY, deltaZ, fof_id, tempGas

    radial_distance = radial_distance[index]
    data.gas.masses = data.gas.masses[index]
    data.gas.temperatures = data.gas.temperatures[index]

    # Define radial bins and shell volumes
    lbins = np.logspace(-3, 2, 40) * radial_distance.units
    radial_bin_centres = 10.0**(
        0.5 * np.log10(lbins[1:] * lbins[:-1])) * radial_distance.units
    volume_shell = (4. * np.pi / 3.) * (R500**3) * ((lbins[1:])**3 -
                                                    (lbins[:-1])**3)

    mass_weights, _ = histogram_unyt(radial_distance,
                                     bins=lbins,
                                     weights=data.gas.masses)
    mass_weights[mass_weights == 0] = np.nan  # Replace zeros with Nans
    density_profile = mass_weights / volume_shell
    number_density_profile = (density_profile.to('g/cm**3') /
                              (unyt.mp * mean_molecular_weight)).to('cm**-3')

    mass_weighted_temperatures = (
        data.gas.temperatures *
        unyt.boltzmann_constant).to('keV') * data.gas.masses
    temperature_weights, _ = histogram_unyt(radial_distance,
                                            bins=lbins,
                                            weights=mass_weighted_temperatures)
    temperature_weights[temperature_weights ==
                        0] = np.nan  # Replace zeros with Nans
    temperature_profile = temperature_weights / mass_weights  # kBT in units of [keV]

    entropy_profile = temperature_profile / number_density_profile**(2 / 3)

    rho_crit = unyt.unyt_quantity(
        data.metadata.cosmology.critical_density(data.metadata.z).value,
        'g/cm**3').to('Msun/Mpc**3')
    density_profile /= rho_crit

    return radial_bin_centres, density_profile, temperature_profile, entropy_profile, M500, R500
def to_swiftsimio_dataset(
    particles: VelociraptorParticles,
    snapshot_filename,
    generate_extra_mask: bool = False,
) -> Union[
    swiftsimio.reader.SWIFTDataset, Tuple[swiftsimio.reader.SWIFTDataset, namedtuple]
]:
    """
    Loads a VelociraptorParticles instance for one halo into a 
    `swiftsimio` masked dataset.

    Initially, this uses `r_max` to perform a spatial mask, and
    then returns the `swiftsimio` dataset and a secondary mask
    that may be used to extract only the particles that are
    part of the FoF group.

    You will need to instantiate the VelociraptorParticles instance
    with an associated catalogue to use this feature, as it requires
    the knowledge of `r_max`.
    
    Takes three arguments:

    + particles, the VelociraptorParticles instance,
    + snapshot_filename, the path to the associated SWIFT snapshot.
    + generate_extra_mask, whether or not to generate the secondary 
                           mask object that allows for the extraction
                           of particles that are present only in the
                           FoF group.

    It returns:

    + data, the swiftsimio dataset
    + mask, an object containing for all available datasets in the
            swift dataset. The initial masking is performed on a
            spatial only basis, and this is required to only extract
            the particles in the FoF group as identified by 
            velociraptor. This is only provided if generate_extra_mask
            has a truthy value.
    """

    # First use the swiftsimio spatial masking to constrain our dataset
    # to only contain particles within the cube that contains the halo
    # (this is only approximate down to the swift cell size)
    swift_mask = swiftsimio.mask(snapshot_filename, spatial_only=True)

    # SWIFT data is stored in comoving units, so we need to un-correct
    # the velociraptor data if it is stored in physical.
    try:
        if not particles.groups_instance.catalogue.units.comoving:
            length_factor = particles.groups_instance.catalogue.units.a
        else:
            length_factor = 1.0
    except AttributeError:
        raise RuntimeError(
            "Please use a particles instance with an associated halo catalogue."
        )

    spatial_mask = [
        [
            particles.x / length_factor - particles.r_size / length_factor,
            particles.x / length_factor + particles.r_size / length_factor,
        ],
        [
            particles.y / length_factor - particles.r_size / length_factor,
            particles.y / length_factor + particles.r_size / length_factor,
        ],
        [
            particles.z / length_factor - particles.r_size / length_factor,
            particles.z / length_factor + particles.r_size / length_factor,
        ],
    ]

    swift_mask.constrain_spatial(spatial_mask)

    # TODO: Make spatial masking work
    # swift_mask = None

    data = swiftsimio.load(snapshot_filename, mask=swift_mask)

    if not generate_extra_mask:
        return data

    # Now we must generate the secondary mask, for all available
    # particle types.

    particle_name_masks = {}

    for particle_name in data.metadata.present_particle_names:
        # This will change if we ever take advantage of the
        # parttypes available through velociraptor.
        particle_name_masks[particle_name] = np.in1d(
            getattr(data, particle_name).particle_ids, particles.particle_ids
        )

    # Finally we generate a named tuple with the correct fields and
    # fill it with the contents of our dictionary
    MaskTuple = namedtuple("MaskCollection", data.metadata.present_particle_names)
    mask = MaskTuple(**particle_name_masks)

    return data, mask
        R200c.append(vr_file['/R_200crit'][0])
        x.append(vr_file['/Xcminpot'][0])
        y.append(vr_file['/Ycminpot'][0])
        z.append(vr_file['/Zcminpot'][0])


for i in range(len(snap_relative_filepaths)):
    # EAGLE-XL data path
    snapFile = simdata_dirpath + snap_relative_filepaths[i]
    print(f"Rendering {snap_relative_filepaths[i]}...")
    # Load data using mask
    xCen = unyt.unyt_quantity(x[i], unyt.Mpc)
    yCen = unyt.unyt_quantity(y[i], unyt.Mpc)
    zCen = unyt.unyt_quantity(z[i], unyt.Mpc)
    size = unyt.unyt_quantity(out_to_radius * R200c[i], unyt.Mpc)
    mask = sw.mask(snapFile)
    region = [
        [xCen - size, xCen + size],
        [yCen - size, yCen + size],
        [zCen - size, zCen + size]
    ]
    mask.constrain_spatial(region)

    # Load data using mask
    data = sw.load(snapFile, mask=mask)
    posDM = data.dark_matter.coordinates / data.metadata.a
    coord_x = posDM[:, 0] - xCen
    coord_y = posDM[:, 1] - yCen
    coord_z = posDM[:, 2] - zCen
    del posDM
def profile_3d_single_halo(
    path_to_snap: str,
    path_to_catalogue: str,
    hse_dataset: pd.Series = None,
) -> tuple:
    # Read in halo properties
    vr_catalogue_handle = vr.load(path_to_catalogue)
    a = vr_catalogue_handle.a
    M500 = vr_catalogue_handle.spherical_overdensities.mass_500_rhocrit[0].to(
        'Msun')
    R500 = vr_catalogue_handle.spherical_overdensities.r_500_rhocrit[0].to(
        'Mpc')
    XPotMin = vr_catalogue_handle.positions.xcminpot[0].to('Mpc')
    YPotMin = vr_catalogue_handle.positions.ycminpot[0].to('Mpc')
    ZPotMin = vr_catalogue_handle.positions.zcminpot[0].to('Mpc')

    # If no custom aperture, select r500c as default
    if hse_dataset is not None:
        assert R500.units == hse_dataset["R500hse"].units
        assert M500.units == hse_dataset["M500hse"].units
        R500 = hse_dataset["R500hse"]
        M500 = hse_dataset["M500hse"]

    # Apply spatial mask to particles. SWIFTsimIO needs comoving coordinates
    # to filter particle coordinates, while VR outputs are in physical units.
    # Convert the region bounds to comoving, but keep the CoP and Rcrit in
    # physical units for later use.
    mask = sw.mask(path_to_snap, spatial_only=True)
    region = [[(XPotMin - R500) * a, (XPotMin + R500) * a],
              [(YPotMin - R500) * a, (YPotMin + R500) * a],
              [(ZPotMin - R500) * a, (ZPotMin + R500) * a]]
    mask.constrain_spatial(region)
    data = sw.load(path_to_snap, mask=mask)

    # Convert datasets to physical quantities
    # r500c is already in physical units
    data.gas.coordinates.convert_to_physical()
    data.gas.masses.convert_to_physical()
    data.gas.temperatures.convert_to_physical()
    data.gas.densities.convert_to_physical()

    # Select hot gas within sphere
    tempGas = data.gas.temperatures
    deltaX = data.gas.coordinates[:, 0] - XPotMin
    deltaY = data.gas.coordinates[:, 1] - YPotMin
    deltaZ = data.gas.coordinates[:, 2] - ZPotMin
    radial_distance = np.sqrt(deltaX**2 + deltaY**2 + deltaZ**2) / R500
    index = np.where((radial_distance < 2) & (tempGas > 1e5))[0]
    del tempGas, deltaX, deltaY, deltaZ

    # Calculate particle mass and rho_crit
    rho_crit = unyt.unyt_quantity(
        data.metadata.cosmology.critical_density(data.metadata.z).value,
        'g/cm**3')

    mass_weighted_temperatures = (data.gas.temperatures *
                                  unyt.boltzmann_constant).to('keV')
    number_densities = (data.gas.densities.to('g/cm**3') /
                        (unyt.mp * mean_molecular_weight)).to('cm**-3')
    field_value = mass_weighted_temperatures / number_densities**(2 / 3)

    field_label = r'$K$ [keV cm$^2$]'
    radial_distance = radial_distance[index]
    field_value = field_value[index]
    field_masses = data.gas.temperatures[index]

    return radial_distance, field_value, field_masses, field_label, M500, R500
Beispiel #21
0
def process_single_halo(path_to_snap: str, path_to_catalogue: str):
    # Read in halo properties
    with h5py.File(f'{path_to_catalogue}', 'r') as h5file:
        M500c = unyt.unyt_quantity(h5file['/SO_Mass_500_rhocrit'][0] * 1.e10,
                                   unyt.Solar_Mass)
        R500c = unyt.unyt_quantity(h5file['/SO_R_500_rhocrit'][0], unyt.Mpc)
        XPotMin = unyt.unyt_quantity(h5file['/Xcminpot'][0], unyt.Mpc)
        YPotMin = unyt.unyt_quantity(h5file['/Ycminpot'][0], unyt.Mpc)
        ZPotMin = unyt.unyt_quantity(h5file['/Zcminpot'][0], unyt.Mpc)

    # Read in gas particles to compute the core-excised temperature
    mask = sw.mask(f'{path_to_snap}', spatial_only=False)
    region = [[XPotMin - 5 * R500c, XPotMin + 5 * R500c],
              [YPotMin - 5 * R500c, YPotMin + 5 * R500c],
              [ZPotMin - 5 * R500c, ZPotMin + 5 * R500c]]
    mask.constrain_spatial(region)
    mask.constrain_mask("gas", "temperatures", 1.e5 * mask.units.temperature,
                        1.e10 * mask.units.temperature)

    data = sw.load(f'{path_to_snap}', mask=mask)

    # Select hot gas within sphere and without core
    deltaX = data.gas.coordinates[:, 0] - XPotMin
    deltaY = data.gas.coordinates[:, 1] - YPotMin
    deltaZ = data.gas.coordinates[:, 2] - ZPotMin
    deltaR = np.sqrt(deltaX**2 + deltaY**2 + deltaZ**2)

    # Keep only particles inside 5 R500crit
    index = np.where((deltaR > 0.15 * R500c) & (deltaR < 5 * R500c))[0]
    data.gas.radial_distances = deltaR[index]
    data.gas.densities = data.gas.densities[index]
    data.gas.masses = data.gas.masses[index]
    data.gas.temperatures = data.gas.temperatures[index]

    data.gas.element_mass_fractions.hydrogen = data.gas.element_mass_fractions.hydrogen[
        index]
    data.gas.element_mass_fractions.helium = data.gas.element_mass_fractions.helium[
        index]
    data.gas.element_mass_fractions.carbon = data.gas.element_mass_fractions.carbon[
        index]
    data.gas.element_mass_fractions.nitrogen = data.gas.element_mass_fractions.nitrogen[
        index]
    data.gas.element_mass_fractions.oxygen = data.gas.element_mass_fractions.oxygen[
        index]
    data.gas.element_mass_fractions.neon = data.gas.element_mass_fractions.neon[
        index]
    data.gas.element_mass_fractions.magnesium = data.gas.element_mass_fractions.magnesium[
        index]
    data.gas.element_mass_fractions.silicon = data.gas.element_mass_fractions.silicon[
        index]
    data.gas.element_mass_fractions.iron = data.gas.element_mass_fractions.iron[
        index]

    spec_fit_data = calc_spectrum(data, R500c)
    fit_spectrum(spec_fit_data)
    # print('Rspec', spec_fit_data['Rspec'])
    # print('Tspec', spec_fit_data['Tspec'])
    # print('RHOspec', spec_fit_data['RHOspec'])
    # print('Zspec', spec_fit_data['Zspec'])
    # print('XIspec', spec_fit_data['XIspec'])

    hse_true = HydrostaticEstimator.from_data_paths(
        catalog_file=path_to_catalogue,
        snapshot_file=path_to_snap,
        profile_type='true')

    hse_spec = HydrostaticEstimator.from_data_paths(
        catalog_file=path_to_catalogue,
        snapshot_file=path_to_snap,
        profile_type='spec',
        spec_fit_data=spec_fit_data)

    print(f'r500c = {hse_spec.r500c:.3E}')
    print(f'M500c = {hse_spec.m500c:.3E}')
    print()
    print(f'R500,true,hse = {hse_true.R500hse:.3E}')
    print(f'M500,true,hse = {hse_true.M500hse:.3E}')
    print(f'P500,true,hse = {hse_true.P500hse:.3E}')
    print(f'kBT500,true,hse = {hse_true.kBT500hse:.3E}')
    print(f'K500,true,hse = {hse_true.K500hse:.3E}')
    print()
    print(f'R500,spec,hse = {hse_spec.R500hse:.3E}')
    print(f'M500,spec,hse = {hse_spec.M500hse:.3E}')
    print(f'P500,spec,hse = {hse_spec.P500hse:.3E}')
    print(f'kBT500,spec,hse = {hse_spec.kBT500hse:.3E}')
    print(f'K500,spec,hse = {hse_spec.K500hse:.3E}')
    print()
    print(f"[Bias] M500 (hse/true) = {hse_true.M500hse / hse_spec.m500c:.3f}")
    print(f"[Bias] M500 (spec/true) = {hse_spec.M500hse / hse_spec.m500c:.3f}")

    return hse_spec
Beispiel #22
0
    def total_mass_profiles(self):

        # Read in halo properties from catalog
        vr_catalogue_handle = vr.load(self.zoom.catalogue_properties_path)
        a = vr_catalogue_handle.a
        self.r500c = vr_catalogue_handle.spherical_overdensities.r_500_rhocrit[
            0].to('Mpc')
        self.r2500c = vr_catalogue_handle.spherical_overdensities.r_2500_rhocrit[
            0].to('Mpc')
        XPotMin = vr_catalogue_handle.positions.xcminpot[0].to('Mpc')
        YPotMin = vr_catalogue_handle.positions.ycminpot[0].to('Mpc')
        ZPotMin = vr_catalogue_handle.positions.zcminpot[0].to('Mpc')

        # Read in gas particles and parse densities and temperatures
        mask = sw.mask(self.zoom.snapshot_path, spatial_only=False)
        region = [[(XPotMin - 1.5 * self.r500c) / a,
                   (XPotMin + 1.5 * self.r500c) / a],
                  [(YPotMin - 1.5 * self.r500c) / a,
                   (YPotMin + 1.5 * self.r500c) / a],
                  [(ZPotMin - 1.5 * self.r500c) / a,
                   (ZPotMin + 1.5 * self.r500c) / a]]

        mask.constrain_spatial(region)
        mask.constrain_mask("gas", "temperatures",
                            Tcut_halogas * mask.units.temperature,
                            1.e12 * mask.units.temperature)
        data = sw.load(self.zoom.snapshot_path, mask=mask)
        self.fbary = Cosmology().get_baryon_fraction(data.metadata.z)

        # Convert datasets to physical quantities
        # r500c is already in physical units
        data.gas.coordinates.convert_to_physical()
        data.gas.masses.convert_to_physical()
        data.gas.temperatures.convert_to_physical()
        data.gas.densities.convert_to_physical()
        data.dark_matter.coordinates.convert_to_physical()
        data.dark_matter.masses.convert_to_physical()
        data.stars.coordinates.convert_to_physical()
        data.stars.masses.convert_to_physical()

        # Set bounds for the radial profiles
        radius_bounds = [0.15, 1.5]
        lbins = np.logspace(np.log10(radius_bounds[0]),
                            np.log10(radius_bounds[1]),
                            true_data_nbins) * dimensionless

        shell_volume = (4 / 3 * np.pi) * self.r500c**3 * (lbins[1:]**3 -
                                                          lbins[:-1]**3)

        critical_density = unyt_quantity(
            data.metadata.cosmology.critical_density(data.metadata.z).value,
            'g/cm**3').to('Msun/Mpc**3')

        # Select hot gas within sphere and without core
        deltaX = data.gas.coordinates[:, 0] - XPotMin
        deltaY = data.gas.coordinates[:, 1] - YPotMin
        deltaZ = data.gas.coordinates[:, 2] - ZPotMin
        deltaR = np.sqrt(deltaX**2 + deltaY**2 + deltaZ**2) / self.r500c

        # Keep only particles inside 1.5 R500crit
        index = np.where(deltaR < radius_bounds[1])[0]
        central_mass = sum(
            data.gas.masses[np.where(deltaR < radius_bounds[0])[0]])
        mass_weights, _ = histogram_unyt(deltaR[index],
                                         bins=lbins,
                                         weights=data.gas.masses[index])

        self.density_profile_input = mass_weights / shell_volume / critical_density

        # Select DM within sphere and without core
        deltaX = data.dark_matter.coordinates[:, 0] - XPotMin
        deltaY = data.dark_matter.coordinates[:, 1] - YPotMin
        deltaZ = data.dark_matter.coordinates[:, 2] - ZPotMin
        deltaR = np.sqrt(deltaX**2 + deltaY**2 + deltaZ**2) / self.r500c

        # Keep only particles inside 1.5 R500crit
        index = np.where(deltaR < radius_bounds[1])[0]
        central_mass += sum(
            data.dark_matter.masses[np.where(deltaR < radius_bounds[0])[0]])
        _mass_weights, _ = histogram_unyt(
            deltaR[index], bins=lbins, weights=data.dark_matter.masses[index])
        mass_weights += _mass_weights

        # Select stars within sphere and without core
        deltaX = data.stars.coordinates[:, 0] - XPotMin
        deltaY = data.stars.coordinates[:, 1] - YPotMin
        deltaZ = data.stars.coordinates[:, 2] - ZPotMin
        deltaR = np.sqrt(deltaX**2 + deltaY**2 + deltaZ**2) / self.r500c

        # Keep only particles inside 1.5 R500crit
        index = np.where(deltaR < radius_bounds[1])[0]
        central_mass += sum(
            data.stars.masses[np.where(deltaR < radius_bounds[0])[0]])
        _mass_weights, _ = histogram_unyt(deltaR[index],
                                          bins=lbins,
                                          weights=data.stars.masses[index])
        mass_weights += _mass_weights

        # Replace zeros with Nans
        mass_weights[mass_weights == 0] = np.nan
        cumulative_mass = central_mass + cumsum_unyt(mass_weights)

        self.radial_bin_centres_input = 10.0**(
            0.5 * np.log10(lbins[1:] * lbins[:-1])) * dimensionless
        self.cumulative_mass_input = cumulative_mass.to('Msun')
        self.total_density_profile_input = mass_weights / shell_volume / critical_density
Beispiel #23
0
def density_profile_parent_plot(halo_id: int,
                                author: str,
                                snap_filepath_parent: str = None,
                                output_directory: str = None) -> None:

    # Load VR output gathered from the halo selection process
    lines = np.loadtxt(f"{output_directory}/halo_selected_{author}.txt",
                       comments="#",
                       delimiter=",",
                       unpack=False).T
    M200c = lines[1] * 1e13
    R200c = lines[2]
    Xcminpot = lines[3]
    Ycminpot = lines[4]
    Zcminpot = lines[5]
    M200c = unyt.unyt_quantity(M200c[halo_id], unyt.Solar_Mass)
    R200c = unyt.unyt_quantity(R200c[halo_id], unyt.Mpc)
    xCen = unyt.unyt_quantity(Xcminpot[halo_id], unyt.Mpc)
    yCen = unyt.unyt_quantity(Ycminpot[halo_id], unyt.Mpc)
    zCen = unyt.unyt_quantity(Zcminpot[halo_id], unyt.Mpc)

    # Construct spatial mask to feed into swiftsimio
    size = radius_bounds[1] * R200c
    mask = sw.mask(snap_filepath_parent)
    region = [[xCen - size, xCen + size], [yCen - size, yCen + size],
              [zCen - size, zCen + size]]
    mask.constrain_spatial(region)
    data = sw.load(snap_filepath_parent, mask=mask)

    # Get DM particle coordinates and compute radial distance from CoP in R200 units
    posDM = data.dark_matter.coordinates / data.metadata.a
    r = np.sqrt((posDM[:, 0] - xCen)**2 + (posDM[:, 1] - yCen)**2 +
                (posDM[:, 2] - zCen)**2) / R200c

    # Calculate particle mass and rho_crit
    unitLength = data.metadata.units.length
    unitMass = data.metadata.units.mass
    rho_crit = unyt.unyt_quantity(
        data.metadata.cosmology['Critical density [internal units]'],
        unitMass / unitLength**3)
    rhoMean = rho_crit * data.metadata.cosmology['Omega_m']
    vol = data.metadata.boxsize[0]**3
    numPart = data.metadata.n_dark_matter
    particleMass = rhoMean * vol / numPart
    parent_mass_resolution = particleMass

    # Construct bins and compute density profile
    lbins = np.logspace(np.log10(radius_bounds[0]), np.log10(radius_bounds[1]),
                        bins)
    hist, bin_edges = np.histogram(r, bins=lbins)
    bin_centre = np.sqrt(bin_edges[1:] * bin_edges[:-1])
    volume_shell = (4. * np.pi / 3.) * (R200c**3) * ((bin_edges[1:])**3 -
                                                     (bin_edges[:-1])**3)
    densities = hist * particleMass / volume_shell / rho_crit

    # Plot density profile for each selected halo in volume
    fig, ax = plt.subplots()
    parent_label = f'Parent: $m_\\mathrm{{DM}} = {latex_float(parent_mass_resolution.value[0])}\\ {parent_mass_resolution.units.latex_repr}$'
    ax.plot(bin_centre, densities, c="lime", linestyle="-", label=parent_label)

    ax.text(
        0.025,
        0.025,
        (f"Halo {halo_id:d} DMO\n"
         f"$z={data.metadata.z:3.3f}$\n"
         "Parent VR output:\n"
         f"$M_{{200c}}={latex_float(M200c.value)}\\ {M200c.units.latex_repr}$\n"
         f"$R_{{200c}}={latex_float(R200c.value)}\\ {R200c.units.latex_repr}$"
         ),
        color="black",
        ha="left",
        va="bottom",
        transform=ax.transAxes,
    )

    ax.set_xlim(radius_bounds[0], radius_bounds[1])
    ax.set_xscale('log')
    ax.set_yscale('log')
    ax.set_ylabel(r"$\rho_{DM}\ /\ \rho_c$")
    ax.set_xlabel(r"$R\ /\ R_{200c}$")
    plt.legend()
    fig.tight_layout()
    fig.savefig(
        f"{output_directory}/halo{halo_id}{author}_density_profile_parent.png")
    plt.close(fig)

    return
Beispiel #24
0
def visualise_halo(
    output_path: Path,
    snapshot_path: Path,
    config: ImageConfig,
    halo: Halo,
):
    """
    Creates all of the visualisations in the config for the
    specified halo, and saves them to disk.

    Parameters
    ----------

    output_path: Path, str
        Output path to save images to. Inside this path, there will be
        a number of directories created (one per halo). This path must
        already exist.

    snapshot_path: Path,
        Path to the snapshot. For a sufficiently large volume, and
        a sufficiently small number of haloes, there will be little-to
        -no overlap in the read regions.

    config: ImageConfig
        Opened configuration file.

    halo: Halo
        The halo to read the data for and visualise.
    """

    # First need to find the maximum radius, amongst any
    # of the images.
    radii = [
        image.get_radius(stellar_half_mass=halo.radius_100_kpc_star,
                         r_200_crit=halo.radius_200_crit)
        for image in config.images
    ]

    max_radius = max(radii)

    extent_given_r = lambda r: [[halo.position[x] - r, halo.position[x] + r]
                                for x in range(3)]

    halo_mask = mask(filename=snapshot_path, spatial_only=True)
    halo_mask.constrain_spatial(restrict=extent_given_r(max_radius))

    data = load(filename=snapshot_path, mask=halo_mask)

    # Generate the smoothing lengths if required.
    if config.calculate_dark_matter_smoothing_lengths:
        data.dark_matter.smoothing_lengths = generate_smoothing_lengths(
            coordinates=data.dark_matter.coordinates,
            boxsize=data.metadata.boxsize,
            kernel_gamma=kernel_gamma,
        )

    if config.recalculate_stellar_smoothing_lengths and hasattr(data, "stars"):
        if len(data.stars.coordinates) > 0:
            data.stars.smoothing_lengths = generate_smoothing_lengths(
                coordinates=data.stars.coordinates,
                boxsize=data.metadata.boxsize,
                kernel_gamma=kernel_gamma,
            )

    halo_directory = output_path / f"halo_{halo.unique_id}"
    halo_directory.mkdir(exist_ok=True)

    for image in config.images:
        # Which projections should we make?
        projections = [Projection.DEFAULT]

        if image.face_on:
            projections.append(Projection.FACE_ON)

        if image.edge_on:
            projections.append(Projection.EDGE_ON)

        for projection in projections:
            scatter = create_scatter(
                snapshot=data,
                halo=halo,
                image=image,
                projection=projection,
                resolution=config.resolution,
            )

            save_figure_from_scatter(
                scatter=scatter,
                config=config,
                halo=halo,
                image=image,
                projection=projection,
                output_path=halo_directory,
            )

            if (projection == Projection.DEFAULT
                    and image.base_name == config.thumbnail_image):
                save_thumbnail_from_scatter(
                    scatter=scatter,
                    config=config,
                    halo=halo,
                    image=image,
                    projection=projection,
                    output_path=halo_directory,
                )
Beispiel #25
0
def density_profile_compare_plot(
        run_name: str,
        snap_filepath_parent: str = None,
        velociraptor_properties_parent: str = None,
        snap_filepath_zoom: List[str] = None,
        velociraptor_properties_zoom: List[str] = None,
        output_directory: str = None) -> None:
    """
    This function compares the density profiles of DMO zooms to their
    corresponding parent halo. It also allows to assess numerical
    convergence by overlapping multiple profiles from zooms with different
    resolutions. The density profiles are then listed in the legend,
    where the DM particle mass is quoted.
    The zoom inputs are in the form of arrays of strings, to allow
    for multiple entries. Each entry is a zoom snap/VR output resolution.
    The function allows not to plot either the parent density profile
    or the zooms. At least one of them must be entered.

    :param run_name: str
        A custom and identifiable name for the run. Currently the standard
        name follows the scheme {AUTHOR_INITIALS}{HALO_ID}, e.g. SK0 or EA1.
        This argument must be defined.
    :param snap_filepath_parent: str
        The complete path to the snapshot of the parent box.
        If parameter is None, the density ptofile of the parent box is not
        displayed.
    :param snap_filepath_zoom: list(str)
        The list of complete paths to the snapshots of the zooms
        at different resolution. Note: the order must match that of
        the `velociraptor_properties_zoom` parameter. If parameter
        is None, the density ptofile of the zoom is not displayed and the
        `velociraptor_properties_zoom` parameter is ignored.
    :param velociraptor_properties_zoom: list(str)
        The list of complete paths to the VR outputs (properties) of the
        zooms at different resolution. Note: the order must match that of
        the `snap_filepath_zoom` parameter. If `snap_filepath_zoom` is None,
        then this parameter is ignored. If this parameter is None and
        `snap_filepath_zoom` is defined, raises an error.
    :param output_directory: str
        The output directory where to save the plot. This code assumes
        that the output directory exists. If it does not exist, matplotlib
        will return an error. This argument must be defined.
    :return: None
    """

    # ARGS CHECK #
    assert snap_filepath_parent or snap_filepath_zoom
    if snap_filepath_zoom and velociraptor_properties_zoom:
        assert len(snap_filepath_zoom) == len(velociraptor_properties_zoom)
    elif not snap_filepath_zoom:
        velociraptor_properties_zoom = None
    elif snap_filepath_zoom and not velociraptor_properties_zoom:
        raise ValueError
    assert output_directory

    fig, (ax,
          ax_residual) = plt.subplots(nrows=2,
                                      ncols=1,
                                      figsize=(7, 8),
                                      dpi=resolution // 8,
                                      sharex=True,
                                      gridspec_kw={'height_ratios': [3, 1]})

    # PARENT #
    if snap_filepath_parent:

        # Rendezvous over parent VR catalogue using zoom information
        with h5py.File(velociraptor_properties_zoom[0], 'r') as vr_file:

            idx, M200c, R200c, Xcminpot, Ycminpot, Zcminpot = find_object(
                vr_properties_catalog=velociraptor_properties_parent,
                sample_structType=10,
                sample_mass_lower_lim=vr_file['/Mass_200crit'][0] * 1e10 * 0.8,
                sample_x=vr_file['/Xcminpot'][0],
                sample_y=vr_file['/Ycminpot'][0],
                sample_z=vr_file['/Zcminpot'][0],
            )

            M200c = unyt.unyt_quantity(M200c, unyt.Solar_Mass)
            R200c = unyt.unyt_quantity(R200c, unyt.Mpc)
            xCen = unyt.unyt_quantity(Xcminpot, unyt.Mpc)
            yCen = unyt.unyt_quantity(Ycminpot, unyt.Mpc)
            zCen = unyt.unyt_quantity(Zcminpot, unyt.Mpc)

        # Construct spatial mask to feed into swiftsimio
        size = radius_bounds[1] * R200c
        mask = sw.mask(snap_filepath_parent)
        region = [[xCen - size, xCen + size], [yCen - size, yCen + size],
                  [zCen - size, zCen + size]]
        mask.constrain_spatial(region)
        data = sw.load(snap_filepath_parent, mask=mask)

        # Get DM particle coordinates and compute radial distance from CoP in R200 units
        posDM = data.dark_matter.coordinates / data.metadata.a

        r = np.sqrt(
            wrap(posDM[:, 0] - xCen, data.metadata.boxsize[0])**2 +
            wrap(posDM[:, 1] - yCen, data.metadata.boxsize[1])**2 +
            wrap(posDM[:, 2] - zCen, data.metadata.boxsize[2])**2)

        # Calculate particle mass and rho_crit
        unitLength = data.metadata.units.length
        unitMass = data.metadata.units.mass
        rho_crit = unyt.unyt_quantity(
            data.metadata.cosmology_raw['Critical density [internal units]'],
            unitMass / unitLength**3).to('Msun/Mpc**3')
        rhoMean = rho_crit * data.metadata.cosmology.Om0
        vol = data.metadata.boxsize[0]**3
        numPart = data.metadata.n_dark_matter
        particleMass = rhoMean * vol / numPart
        parent_mass_resolution = particleMass
        particleMasses = np.ones_like(r.value) * particleMass

        # Construct bins and compute density profile
        # NOTE: numpy.histogram does not preserve units, so restore them after.
        lbins = np.logspace(np.log10(radius_bounds[0]),
                            np.log10(radius_bounds[1]), bins)
        r_scaled = r / R200c
        hist, bin_edges = np.histogram(r_scaled,
                                       bins=lbins,
                                       weights=particleMasses)
        hist *= unyt.Solar_Mass
        bin_centre = np.sqrt(bin_edges[1:] * bin_edges[:-1])
        volume_shell = (4. * np.pi / 3.) * (R200c**3) * ((bin_edges[1:])**3 -
                                                         (bin_edges[:-1])**3)
        densities_parent = hist / volume_shell / rho_crit

        # Plot density profile for each selected halo in volume
        densities_parent[densities_parent == 0] = np.nan
        parent_label = (
            f'Parent: $m_\\mathrm{{DM}} = {latex_float(parent_mass_resolution.value[0])}\\ '
            f'{parent_mass_resolution.units.latex_repr}$')
        ax.plot(bin_centre,
                densities_parent,
                c="grey",
                linestyle="-",
                label=parent_label)

        # Compute convergence radius
        conv_radius = convergence_radius(r, particleMasses, rho_crit) / R200c
        ax.axvline(conv_radius, color='grey', linestyle='--')
        ax_residual.axvline(conv_radius, color='grey', linestyle='--')
        t = ax.text(conv_radius,
                    ax.get_ylim()[1],
                    'Convergence radius',
                    ha='center',
                    va='top',
                    rotation='vertical',
                    alpha=0.6)
        t.set_bbox(dict(facecolor='white', alpha=0.6, edgecolor='none'))

    # ZOOMS #
    if snap_filepath_zoom:

        # Set-up colors
        cmap_discrete = plt.cm.get_cmap(cmap_name,
                                        len(velociraptor_properties_zoom) + 3)
        cmaplist = [cmap_discrete(i) for i in range(cmap_discrete.N)]

        for snap_path, vrprop_path, color in zip(snap_filepath_zoom,
                                                 velociraptor_properties_zoom,
                                                 cmaplist):

            # Load velociraptor data
            with h5py.File(vrprop_path, 'r') as vr_file:
                M200c = vr_file['/Mass_200crit'][0] * 1e10
                R200c = vr_file['/R_200crit'][0]
                Xcminpot = vr_file['/Xcminpot'][0]
                Ycminpot = vr_file['/Ycminpot'][0]
                Zcminpot = vr_file['/Zcminpot'][0]

            M200c = unyt.unyt_quantity(M200c, unyt.Solar_Mass)
            R200c = unyt.unyt_quantity(R200c, unyt.Mpc)
            xCen = unyt.unyt_quantity(Xcminpot, unyt.Mpc)
            yCen = unyt.unyt_quantity(Ycminpot, unyt.Mpc)
            zCen = unyt.unyt_quantity(Zcminpot, unyt.Mpc)

            # Construct spatial mask to feed into swiftsimio
            size = radius_bounds[1] * R200c
            mask = sw.mask(snap_path)
            region = [[xCen - size, xCen + size], [yCen - size, yCen + size],
                      [zCen - size, zCen + size]]
            mask.constrain_spatial(region)
            data = sw.load(snap_path, mask=mask)

            # Get DM particle coordinates and compute radial distance from CoP in R200 units
            posDM = data.dark_matter.coordinates / data.metadata.a

            r = np.sqrt(
                wrap(posDM[:, 0] - xCen, data.metadata.boxsize[0])**2 +
                wrap(posDM[:, 1] - yCen, data.metadata.boxsize[1])**2 +
                wrap(posDM[:, 2] - zCen, data.metadata.boxsize[2])**2)

            # Calculate particle mass and rho_crit
            unitLength = data.metadata.units.length
            unitMass = data.metadata.units.mass
            rho_crit = unyt.unyt_quantity(
                data.metadata.
                cosmology_raw['Critical density [internal units]'],
                unitMass / unitLength**3).to('Msun/Mpc**3')
            particleMasses = data.dark_matter.masses.to('Msun')
            zoom_mass_resolution = particleMasses

            # Construct bins and compute density profile
            # NOTE: numpy.histogram does not preserve units, so restore them after.
            lbins = np.logspace(np.log10(radius_bounds[0]),
                                np.log10(radius_bounds[1]), bins)
            r_scaled = r / R200c
            hist, bin_edges = np.histogram(r_scaled,
                                           bins=lbins,
                                           weights=particleMasses)
            hist *= unyt.Solar_Mass
            bin_centre = np.sqrt(bin_edges[1:] * bin_edges[:-1])
            volume_shell = (4. * np.pi / 3.) * (R200c**3) * (
                (bin_edges[1:])**3 - (bin_edges[:-1])**3)
            densities_zoom = hist / volume_shell / rho_crit

            # Plot density profile for each selected halo in volume
            densities_zoom[densities_zoom == 0] = np.nan
            zoom_label = (
                f'Zoom: $m_\\mathrm{{DM}} = {latex_float(zoom_mass_resolution.value[0])}\\ '
                f'{zoom_mass_resolution.units.latex_repr}$')
            ax.plot(bin_centre,
                    densities_zoom,
                    c=color,
                    linestyle="-",
                    label=zoom_label)

            # Compute convergence radius
            conv_radius = convergence_radius(r, particleMasses,
                                             rho_crit) / R200c
            ax.axvline(conv_radius, color=color, linestyle='--')
            t = ax.text(conv_radius,
                        ax.get_ylim()[1],
                        'Convergence radius',
                        ha='center',
                        va='top',
                        rotation='vertical',
                        alpha=0.5)
            t.set_bbox(dict(facecolor='white', alpha=0.6, edgecolor='none'))

            # RESIDUALS #
            if snap_filepath_parent and snap_filepath_zoom:
                residual = (densities_zoom -
                            densities_parent) / densities_parent
                ax_residual.axhline(0, color='grey', linestyle='-')
                ax_residual.plot(bin_centre, residual, c=color, linestyle="-")
                ax_residual.axvline(conv_radius, color=color, linestyle='--')

    ax.text(
        0.025,
        0.025,
        (f"Halo {run_name:s} DMO\n"
         f"$z={data.metadata.z:3.3f}$\n"
         "Zoom VR output:\n"
         f"$M_{{200c}}={latex_float(M200c.value)}\\ {M200c.units.latex_repr}$\n"
         f"$R_{{200c}}={latex_float(R200c.value)}\\ {R200c.units.latex_repr}$"
         ),
        color="black",
        ha="left",
        va="bottom",
        backgroundcolor='white',
        alpha=0.5,
        transform=ax.transAxes,
    )

    ax.axvline(1, color="grey", linestyle='--')
    ax_residual.axvline(1, color="grey", linestyle='--')
    ax_residual.set_xlim(radius_bounds[0], radius_bounds[1])
    ax_residual.set_ylim(residual_bounds[0], residual_bounds[1])
    ax.set_xscale('log')
    ax.set_yscale('log')
    ax.set_ylabel(r"$\rho_{DM}\ /\ \rho_c$")
    ax_residual.set_ylabel(f"$\\Delta \\rho\\ /\\ \\rho_{{\\rm parent}}$")
    ax_residual.set_xlabel(r"$R\ /\ R_{200c}$")
    ax.legend(loc="upper right")
    fig.tight_layout()
    fig.savefig(f"{output_directory}/{run_name}_density_profile_compare.png")
    plt.close(fig)
    print(f"Saved: {output_directory}/{run_name}_density_profile_compare.png")
    plt.close('all')

    return
Beispiel #26
0
def image_snap(isnap):
    """Main function to image one specified snapshot."""

    print(f"Beginning imaging snapshot {isnap}...")
    stime = time.time()

    plotloc = (args.rootdir +
               f'{args.outdir}/image_pt{args.ptype}_{args.imtype}_'
               f'{args.coda}_')
    if args.cambhbid is not None:
        plotloc = plotloc + f'BH-{args.cambhbid}_'
    if not os.path.isdir(os.path.dirname(plotloc)):
        os.makedirs(os.path.dirname(plotloc))
    if not args.replot_existing and os.path.isfile(
            f'{plotloc}{isnap:04d}.png'):
        print(f"Image {plotloc}{isnap:04d}.png already exists, skipping.")
        return

    snapdir = args.rootdir + f'{args.snap_name}_{isnap:04d}.hdf5'

    mask = sw.mask(snapdir)

    # Read metadata
    print("Read metadata...")
    boxsize = max(mask.metadata.boxsize.value)

    ut = hd.read_attribute(snapdir, 'Units', 'Unit time in cgs (U_t)')[0]
    um = hd.read_attribute(snapdir, 'Units', 'Unit mass in cgs (U_M)')[0]
    time_int = hd.read_attribute(snapdir, 'Header', 'Time')[0]
    aexp_factor = hd.read_attribute(snapdir, 'Header', 'Scale-factor')[0]
    zred = hd.read_attribute(snapdir, 'Header', 'Redshift')[0]
    num_part = hd.read_attribute(snapdir, 'Header', 'NumPart_Total')

    time_gyr = time_int * ut / (3600 * 24 * 365.24 * 1e9)
    mdot_factor = (um / 1.989e33) / (ut / (3600 * 24 * 365.24))

    # -----------------------
    # Snapshot-specific setup
    # -----------------------

    # Camera position
    camPos = None
    if vr_halo >= 0:
        print("Reading camera position from VR catalogue...")
        vr_file = args.rootdir + f'vr_{isnap:04d}.hdf5'
        camPos = hd.read_data(vr_file, 'MinimumPotential/Coordinates')

    elif args.varpos is not None:
        print("Find camera position...")
        if len(args.varpos) != 6:
            print("Need 6 arguments for moving box")
            set_trace()
        camPos = np.array([
            args.varpos[0] + args.varpos[3] * time_gyr,
            args.varpos[1] + args.varpos[4] * time_gyr,
            args.varpos[2] + args.varpos[5] * time_gyr
        ])
        print(camPos)
        camPos *= aexp_factor

    elif args.campos is not None:
        camPos = np.array(args.campos) * aexp_factor
    elif args.campos_phys is not None:
        camPos = np.array(args.campos)

    elif args.cambhid is not None:
        all_bh_ids = hd.read_data(snapdir, 'PartType5/ParticleIDs')
        args.cambh = np.nonzero(all_bh_ids == args.cambhid)[0]
        if len(args.cambh) == 0:
            print(f"BH ID {args.cambhid} does not exist, skipping.")
            return

        if len(args.cambh) != 1:
            print(f"Could not unambiguously find BH ID '{args.cambhid}'!")
            set_trace()
        args.cambh = args.cambh[0]

    if args.cambh is not None and camPos is None:
        camPos = hd.read_data(snapdir,
                              'PartType5/Coordinates',
                              read_index=args.cambh) * aexp_factor
        args.hsml = hd.read_data(
            snapdir, 'PartType5/SmoothingLengths',
            read_index=args.cambh) * aexp_factor * kernel_gamma

    elif camPos is None:
        print("Setting camera position to box centre...")
        camPos = np.array([0.5, 0.5, 0.5]) * boxsize * aexp_factor

    # Image size conversion, if necessary
    if not args.propersize:
        args.imsize = args.realimsize * aexp_factor
        args.zsize = args.realzsize * aexp_factor
    else:
        args.imsize = args.realimsize
        args.zsize = args.realzsize

    max_sel = 1.2 * np.sqrt(3) * max(args.imsize, args.zsize)
    extent = np.array([-1, 1, -1, 1]) * args.imsize

    # Set up loading region
    if max_sel < boxsize * aexp_factor / 2:

        load_region = np.array(
            [[camPos[0] - args.imsize * 1.2, camPos[0] + args.imsize * 1.2],
             [camPos[1] - args.imsize * 1.2, camPos[1] + args.imsize * 1.2],
             [camPos[2] - args.zsize * 1.2, camPos[2] + args.zsize * 1.2]])
        load_region = sw.cosmo_array(load_region / aexp_factor, "Mpc")
        mask.constrain_spatial(load_region)
        data = sw.load(snapdir, mask=mask)
    else:
        data = sw.load(snapdir)

    pt_names = ['gas', 'dark_matter', None, None, 'stars', 'black_holes']
    datapt = getattr(data, pt_names[args.ptype])

    pos = datapt.coordinates.value * aexp_factor

    # Next bit does periodic wrapping
    def flip_dim(idim):
        full_box_phys = boxsize * aexp_factor
        half_box_phys = boxsize * aexp_factor / 2
        if camPos[idim] < min(max_sel, half_box_phys):
            ind_high = np.nonzero(pos[:, idim] > half_box_phys)[0]
            pos[ind_high, idim] -= full_box_phys
        elif camPos[idim] > max(full_box_phys - max_sel, half_box_phys):
            ind_low = np.nonzero(pos[:, idim] < half_box_phys)[0]
            pos[ind_low, idim] += full_box_phys

    for idim in range(3):
        print(f"Periodic wrapping in dimension {idim}...")
        flip_dim(idim)

    rad = np.linalg.norm(pos - camPos[None, :], axis=1)
    ind_sel = np.nonzero(rad < max_sel)[0]
    pos = pos[ind_sel, :]

    # Read BH properties, if they exist
    if num_part[5] > 0 and not args.nobh:
        bh_hsml = (hd.read_data(snapdir, 'PartType5/SmoothingLengths') *
                   aexp_factor)
        bh_pos = hd.read_data(snapdir, 'PartType5/Coordinates') * aexp_factor
        bh_mass = hd.read_data(snapdir, 'PartType5/SubgridMasses') * 1e10
        bh_maccr = (hd.read_data(snapdir, 'PartType5/AccretionRates') *
                    mdot_factor)
        bh_id = hd.read_data(snapdir, 'PartType5/ParticleIDs')
        bh_nseed = hd.read_data(snapdir, 'PartType5/CumulativeNumberOfSeeds')
        bh_ft = hd.read_data(snapdir, 'PartType5/FormationScaleFactors')
        print(f"Max BH mass: {np.log10(np.max(bh_mass))}")

    else:
        bh_mass = None  # Dummy value

    # Read the appropriate 'mass' quantity
    if args.ptype == 0 and args.imtype == 'sfr':
        mass = datapt.star_formation_rates[ind_sel]
        mass.convert_to_units(unyt.Msun / unyt.yr)
        mass = np.clip(mass.value, 0, None)  # Don't care about last SFR aExp
    else:
        mass = datapt.masses[ind_sel]
        mass.convert_to_units(unyt.Msun)
        mass = mass.value

    if args.ptype == 0:
        hsml = (datapt.smoothing_lengths.value[ind_sel] * aexp_factor *
                kernel_gamma)
    elif fixedSmoothingLength > 0:
        hsml = np.zeros(mass.shape[0], dtype=np.float32) + fixedSmoothingLength
    else:
        hsml = None

    if args.imtype == 'temp':
        quant = datapt.temperatures.value[ind_sel]
    elif args.imtype == 'diffusion_parameters':
        quant = datapt.diffusion_parameters.value[ind_sel]
    else:
        quant = mass

    # Read quantities for gri computation if necessary
    if args.ptype == 4 and args.imtype == 'gri':
        m_init = datapt.initial_masses.value[ind_sel] * 1e10  # in M_sun
        z_star = datapt.metal_mass_fractions.value[ind_sel]
        sft = datapt.birth_scale_factors.value[ind_sel]

        age_star = (time_gyr - hy.aexp_to_time(sft, time_type='age')) * 1e9
        age_star = np.clip(age_star, 0, None)  # Avoid rounding issues

        lum_g = et.imaging.stellar_luminosity(m_init, z_star, age_star, 'g')
        lum_r = et.imaging.stellar_luminosity(m_init, z_star, age_star, 'r')
        lum_i = et.imaging.stellar_luminosity(m_init, z_star, age_star, 'i')

    # ---------------------
    # Generate actual image
    # ---------------------

    xBase = np.zeros(3, dtype=np.float32)
    yBase = np.copy(xBase)
    zBase = np.copy(xBase)

    if args.imtype == 'gri':
        image_weight_all_g, image_quant, hsml = ir.make_sph_image_new_3d(
            pos,
            lum_g,
            lum_g,
            hsml,
            DesNgb=desNGB,
            imsize=args.numpix,
            zpix=1,
            boxsize=args.imsize,
            CamPos=camPos,
            CamDir=camDir,
            ProjectionPlane=projectionPlane,
            verbose=True,
            CamAngle=[0, 0, rho],
            rollMode=0,
            edge_on=edge_on,
            treeAllocFac=10,
            xBase=xBase,
            yBase=yBase,
            zBase=zBase,
            return_hsml=True)
        image_weight_all_r, image_quant = ir.make_sph_image_new_3d(
            pos,
            lum_r,
            lum_r,
            hsml,
            DesNgb=desNGB,
            imsize=args.numpix,
            zpix=1,
            boxsize=args.imsize,
            CamPos=camPos,
            CamDir=camDir,
            ProjectionPlane=projectionPlane,
            verbose=True,
            CamAngle=[0, 0, rho],
            rollMode=0,
            edge_on=edge_on,
            treeAllocFac=10,
            xBase=xBase,
            yBase=yBase,
            zBase=zBase,
            return_hsml=False)
        image_weight_all_i, image_quant = ir.make_sph_image_new_3d(
            pos,
            lum_i,
            lum_i,
            hsml,
            DesNgb=desNGB,
            imsize=args.numpix,
            zpix=1,
            boxsize=args.imsize,
            CamPos=camPos,
            CamDir=camDir,
            ProjectionPlane=projectionPlane,
            verbose=True,
            CamAngle=[0, 0, rho],
            rollMode=0,
            edge_on=edge_on,
            treeAllocFac=10,
            xBase=xBase,
            yBase=yBase,
            zBase=zBase,
            return_hsml=False)

        map_maas_g = -5 / 2 * np.log10(image_weight_all_g[:, :, 1] +
                                       1e-15) + 5 * np.log10(
                                           180 * 3600 / np.pi) + 25
        map_maas_r = -5 / 2 * np.log10(image_weight_all_r[:, :, 1] +
                                       1e-15) + 5 * np.log10(
                                           180 * 3600 / np.pi) + 25
        map_maas_i = -5 / 2 * np.log10(image_weight_all_i[:, :, 1] +
                                       1e-15) + 5 * np.log10(
                                           180 * 3600 / np.pi) + 25

    else:
        image_weight_all, image_quant = ir.make_sph_image_new_3d(
            pos,
            mass,
            quant,
            hsml,
            DesNgb=desNGB,
            imsize=args.numpix,
            zpix=1,
            boxsize=args.imsize,
            CamPos=camPos,
            CamDir=camDir,
            ProjectionPlane=projectionPlane,
            verbose=True,
            CamAngle=[0, 0, rho],
            rollMode=0,
            edge_on=edge_on,
            treeAllocFac=10,
            xBase=xBase,
            yBase=yBase,
            zBase=zBase,
            zrange=[-args.zsize, args.zsize])

        # Extract surface density in M_sun [/yr] / kpc^2
        sigma = np.log10(image_weight_all[:, :, 1] + 1e-15) - 6
        if args.ptype == 0 and args.imtype in ['temp']:
            tmap = np.log10(image_quant[:, :, 1])
        elif args.ptype == 0 and args.imtype in ['diffusion_parameters']:
            tmap = image_quant[:, :, 1]

    # -----------------
    # Save image data
    # -----------------

    if save_maps:
        maploc = plotloc + f'{isnap:04d}.hdf5'

        if args.imtype == 'gri' and args.ptype == 4:
            hd.write_data(maploc, 'g_maas', map_maas_g, new=True)
            hd.write_data(maploc, 'r_maas', map_maas_r)
            hd.write_data(maploc, 'i_maas', map_maas_i)
        else:
            hd.write_data(maploc, 'Sigma', sigma, new=True)
            if args.ptype == 0 and args.imtype == 'temp':
                hd.write_data(maploc, 'Temperature', tmap)
            elif args.ptype == 0 and args.imtype == 'diffusion_parameters':
                hd.write_data(maploc, 'DiffusionParameters', tmap)

        hd.write_data(maploc, 'Extent', extent)
        hd.write_attribute(maploc, 'Header', 'CamPos', camPos)
        hd.write_attribute(maploc, 'Header', 'ImSize', args.imsize)
        hd.write_attribute(maploc, 'Header', 'NumPix', args.numpix)
        hd.write_attribute(maploc, 'Header', 'Redshift', 1 / aexp_factor - 1)
        hd.write_attribute(maploc, 'Header', 'AExp', aexp_factor)
        hd.write_attribute(maploc, 'Header', 'Time', time_gyr)

        if bh_mass is not None:
            hd.write_data(maploc,
                          'BH_pos',
                          bh_pos - camPos[None, :],
                          comment='Relative position of BHs')
            hd.write_data(maploc,
                          'BH_mass',
                          bh_mass,
                          comment='Subgrid mass of BHs')
            hd.write_data(
                maploc,
                'BH_maccr',
                bh_maccr,
                comment='Instantaneous BH accretion rate in M_sun/yr')
            hd.write_data(maploc,
                          'BH_id',
                          bh_id,
                          comment='Particle IDs of BHs')
            hd.write_data(maploc,
                          'BH_nseed',
                          bh_nseed,
                          comment='Number of seeds in each BH')
            hd.write_data(maploc,
                          'BH_aexp',
                          bh_ft,
                          comment='Formation scale factor of each BH')

    # -------------
    # Plot image...
    # -------------

    if not args.noplot:

        print("Obtained image, plotting...")
        fig = plt.figure(figsize=(args.inch, args.inch))
        ax = fig.add_axes([0.0, 0.0, 1.0, 1.0])
        plt.sca(ax)

        # Option I: we have really few particles. Plot them individually:
        if pos.shape[0] < 32:
            plt.scatter(pos[:, 0] - camPos[0],
                        pos[:, 1] - camPos[1],
                        color='white')

        else:
            # Main plotting regime

            # Case A: gri image -- very different from rest
            if args.ptype == 4 and args.imtype == 'gri':

                vmin = -args.scale[0] + np.array([-0.5, -0.25, 0.0])
                vmax = -args.scale[1] + np.array([-0.5, -0.25, 0.0])

                clmap_rgb = np.zeros((args.numpix, args.numpix, 3))
                clmap_rgb[:, :, 2] = np.clip(
                    ((-map_maas_g) - vmin[0]) / ((vmax[0] - vmin[0])), 0, 1)
                clmap_rgb[:, :, 1] = np.clip(
                    ((-map_maas_r) - vmin[1]) / ((vmax[1] - vmin[1])), 0, 1)
                clmap_rgb[:, :, 0] = np.clip(
                    ((-map_maas_i) - vmin[2]) / ((vmax[2] - vmin[2])), 0, 1)

                im = plt.imshow(clmap_rgb,
                                extent=extent,
                                aspect='equal',
                                interpolation='nearest',
                                origin='lower',
                                alpha=1.0)

            else:

                # Establish image scaling
                if not args.absscale:
                    ind_use = np.nonzero(sigma > 1e-15)
                    vrange = np.percentile(sigma[ind_use], args.scale)
                else:
                    vrange = args.scale
                print(f'Sigma range: {vrange[0]:.4f} -- {vrange[1]:.4f}')

                # Case B: temperature/diffusion parameter image
                if (args.ptype == 0
                        and args.imtype in ['temp', 'diffusion_parameters']
                        and not args.no_double_image):
                    if args.imtype == 'temp':
                        cmap = None
                    elif args.imtype == 'diffusion_parameters':
                        cmap = cmocean.cm.haline
                    clmap_rgb = ir.make_double_image(
                        sigma,
                        tmap,
                        percSigma=vrange,
                        absSigma=True,
                        rangeQuant=args.quantrange,
                        cmap=cmap)

                    im = plt.imshow(clmap_rgb,
                                    extent=extent,
                                    aspect='equal',
                                    interpolation='nearest',
                                    origin='lower',
                                    alpha=1.0)

                else:
                    # Standard sigma images
                    if args.ptype == 0:
                        if args.imtype == 'hi':
                            cmap = plt.cm.bone
                        elif args.imtype == 'sfr':
                            cmap = plt.cm.magma
                        elif args.imtype == 'diffusion_parameters':
                            cmap = cmocean.cm.haline
                        else:
                            cmap = plt.cm.inferno

                    elif args.ptype == 1:
                        cmap = plt.cm.Greys_r
                    elif args.ptype == 4:
                        cmap = plt.cm.bone

                    if args.no_double_image:
                        plotquant = tmap
                        vmin, vmax = args.quantrange[0], args.quantrange[1]
                    else:
                        plotquant = sigma
                        vmin, vmax = vrange[0], vrange[1]

                    im = plt.imshow(plotquant,
                                    cmap=cmap,
                                    extent=extent,
                                    vmin=vmin,
                                    vmax=vmax,
                                    origin='lower',
                                    interpolation='nearest',
                                    aspect='equal')

        # Plot BHs if desired:
        if show_bhs and bh_mass is not None:

            if args.bh_file is not None:
                bh_inds = np.loadtxt(args.bh_file, dtype=int)
            else:
                bh_inds = np.arange(bh_pos.shape[0])

            ind_show = np.nonzero(
                (np.abs(bh_pos[bh_inds, 0] - camPos[0]) < args.imsize)
                & (np.abs(bh_pos[bh_inds, 1] - camPos[1]) < args.imsize)
                & (np.abs(bh_pos[bh_inds, 2] - camPos[2]) < args.zsize)
                & (bh_ft[bh_inds] >= args.bh_ftrange[0])
                & (bh_ft[bh_inds] <= args.bh_ftrange[1])
                & (bh_mass[bh_inds] >= 10.0**args.bh_mrange[0])
                & (bh_mass[bh_inds] <= 10.0**args.bh_mrange[1]))[0]
            ind_show = bh_inds[ind_show]

            if args.bh_quant == 'mass':
                sorter = np.argsort(bh_mass[ind_show])
                sc = plt.scatter(bh_pos[ind_show[sorter], 0] - camPos[0],
                                 bh_pos[ind_show[sorter], 1] - camPos[1],
                                 marker='o',
                                 c=np.log10(bh_mass[ind_show[sorter]]),
                                 edgecolor='grey',
                                 vmin=5.0,
                                 vmax=args.bh_mmax,
                                 s=5.0,
                                 linewidth=0.2)
                bticks = np.linspace(5.0, args.bh_mmax, num=6, endpoint=True)
                blabel = r'log$_{10}$ ($m_\mathrm{BH}$ [M$_\odot$])'

            elif args.bh_quant == 'formation':
                sorter = np.argsort(bh_ft[ind_show])
                sc = plt.scatter(bh_pos[ind_show[sorter], 0] - camPos[0],
                                 bh_pos[ind_show[sorter], 1] - camPos[1],
                                 marker='o',
                                 c=bh_ft[ind_show[sorter]],
                                 edgecolor='grey',
                                 vmin=0,
                                 vmax=1.0,
                                 s=5.0,
                                 linewidth=0.2)
                bticks = np.linspace(0.0, 1.0, num=6, endpoint=True)
                blabel = 'Formation scale factor'

            if args.bhind:
                for ibh in ind_show[sorter]:
                    c = plt.cm.viridis(
                        (np.log10(bh_mass[ibh]) - 5.0) / (args.bh_mmax - 5.0))
                    plt.text(bh_pos[ibh, 0] - camPos[0] + args.imsize / 200,
                             bh_pos[ibh, 1] - camPos[1] + args.imsize / 200,
                             f'{ibh}',
                             color=c,
                             fontsize=4,
                             va='bottom',
                             ha='left')

            if args.draw_hsml:
                phi = np.arange(0, 2.01 * np.pi, 0.01)
                plt.plot(args.hsml * np.cos(phi),
                         args.hsml * np.sin(phi),
                         color='white',
                         linestyle=':',
                         linewidth=0.5)

            # Add colour bar for BH masses
            if args.imtype != 'sfr':
                ax2 = fig.add_axes([0.6, 0.07, 0.35, 0.02])
                ax2.set_xticks([])
                ax2.set_yticks([])
                cbar = plt.colorbar(sc,
                                    cax=ax2,
                                    orientation='horizontal',
                                    ticks=bticks)
                cbar.ax.tick_params(labelsize=8)
                fig.text(0.775,
                         0.1,
                         blabel,
                         rotation=0.0,
                         va='bottom',
                         ha='center',
                         color='white',
                         fontsize=8)

        # Done with main image, some embellishments...
        plt.sca(ax)
        plt.text(-0.045 / 0.05 * args.imsize,
                 0.045 / 0.05 * args.imsize,
                 'z = {:.3f}'.format(1 / aexp_factor - 1),
                 va='center',
                 ha='left',
                 color='white')
        plt.text(-0.045 / 0.05 * args.imsize,
                 0.041 / 0.05 * args.imsize,
                 't = {:.3f} Gyr'.format(time_gyr),
                 va='center',
                 ha='left',
                 color='white',
                 fontsize=8)

        plot_bar()

        # Plot colorbar for SFR if appropriate
        if args.ptype == 0 and args.imtype == 'sfr':
            ax2 = fig.add_axes([0.6, 0.07, 0.35, 0.02])
            ax2.set_xticks([])
            ax2.set_yticks([])

            scc = plt.scatter([-1e10], [-1e10],
                              c=[0],
                              cmap=plt.cm.magma,
                              vmin=vrange[0],
                              vmax=vrange[1])
            cbar = plt.colorbar(scc,
                                cax=ax2,
                                orientation='horizontal',
                                ticks=np.linspace(np.floor(vrange[0]),
                                                  np.ceil(vrange[1]),
                                                  5,
                                                  endpoint=True))
            cbar.ax.tick_params(labelsize=8)
            fig.text(
                0.775,
                0.1,
                r'log$_{10}$ ($\Sigma_\mathrm{SFR}$ [M$_\odot$ yr$^{-1}$ kpc$^{-2}$])',
                rotation=0.0,
                va='bottom',
                ha='center',
                color='white',
                fontsize=8)

        ax.set_xlabel(r'$\Delta x$ [pMpc]')
        ax.set_ylabel(r'$\Delta y$ [pMpc]')

        ax.set_xlim((-args.imsize, args.imsize))
        ax.set_ylim((-args.imsize, args.imsize))

        plt.savefig(plotloc + str(isnap).zfill(4) + '.png',
                    dpi=args.numpix / args.inch)
        plt.close()

    print(f"Finished snapshot {isnap} in {(time.time() - stime):.2f} sec.")
    print(f"Image saved in {plotloc}{isnap:04d}.png")
Beispiel #27
0
def feedback_stats_dT(path_to_snap: str, path_to_catalogue: str) -> dict:
    # Read in halo properties
    with h5py.File(f'{path_to_catalogue}', 'r') as h5file:
        XPotMin = unyt.unyt_quantity(h5file['/Xcminpot'][0], unyt.Mpc)
        YPotMin = unyt.unyt_quantity(h5file['/Ycminpot'][0], unyt.Mpc)
        ZPotMin = unyt.unyt_quantity(h5file['/Zcminpot'][0], unyt.Mpc)
        R500c = unyt.unyt_quantity(h5file['/SO_R_500_rhocrit'][0], unyt.Mpc)

    # Read in particles
    mask = sw.mask(f'{path_to_snap}', spatial_only=True)
    region = [[
        XPotMin - radius_bounds[1] * R500c, XPotMin + radius_bounds[1] * R500c
    ], [
        YPotMin - radius_bounds[1] * R500c, YPotMin + radius_bounds[1] * R500c
    ], [
        ZPotMin - radius_bounds[1] * R500c, ZPotMin + radius_bounds[1] * R500c
    ]]
    mask.constrain_spatial(region)
    data = sw.load(f'{path_to_snap}', mask=mask)

    # Get positions for all BHs in the bounding region
    bh_positions = data.black_holes.coordinates
    bh_coordX = bh_positions[:, 0] - XPotMin
    bh_coordY = bh_positions[:, 1] - YPotMin
    bh_coordZ = bh_positions[:, 2] - ZPotMin
    bh_radial_distance = np.sqrt(bh_coordX**2 + bh_coordY**2 + bh_coordZ**2)

    # The central SMBH will probably be massive.
    # Narrow down the search to the BH with top 5% in mass
    bh_masses = data.black_holes.subgrid_masses.to_physical()
    bh_top_massive_index = np.where(
        bh_masses > np.percentile(bh_masses.value, 95))[0]

    # Get the central BH closest to centre of halo at z=0
    central_bh_index = np.argmin(bh_radial_distance[bh_top_massive_index])
    central_bh_id_target = data.black_holes.particle_ids[bh_top_massive_index][
        central_bh_index]

    # Initialise typed dictionary for the central BH
    central_bh = defaultdict(list)
    central_bh['x'] = []
    central_bh['y'] = []
    central_bh['z'] = []
    central_bh['dx'] = []
    central_bh['dy'] = []
    central_bh['dz'] = []
    central_bh['dr'] = []
    central_bh['mass'] = []
    central_bh['m500c'] = []
    central_bh['id'] = []
    central_bh['redshift'] = []
    central_bh['time'] = []

    # Retrieve BH data from other snaps
    # Clip redshift data (get snaps below that redshifts)
    z_clip = 5.
    all_snaps = get_allpaths_from_last(path_to_snap, z_max=z_clip)
    all_catalogues = get_allpaths_from_last(path_to_catalogue, z_max=z_clip)
    assert len(all_snaps) == len(all_catalogues), (
        f"Detected different number of high-z snaps and high-z catalogues. "
        f"Number of snaps: {len(all_snaps)}. Number of catalogues: {len(all_catalogues)}."
    )

    for i, (highz_snap,
            highz_catalogue) in enumerate(zip(all_snaps, all_catalogues)):

        if not SILENT_PROGRESSBAR:
            print((f"Analysing snap ({i + 1}/{len(all_snaps)}):\n"
                   f"\t{os.path.basename(highz_snap)}\n"
                   f"\t{os.path.basename(highz_catalogue)}"))

        with h5py.File(f'{highz_catalogue}', 'r') as h5file:
            XPotMin = unyt.unyt_quantity(h5file['/Xcminpot'][0],
                                         unyt.Mpc) / data.metadata.a
            YPotMin = unyt.unyt_quantity(h5file['/Ycminpot'][0],
                                         unyt.Mpc) / data.metadata.a
            ZPotMin = unyt.unyt_quantity(h5file['/Zcminpot'][0],
                                         unyt.Mpc) / data.metadata.a
            M500c = unyt.unyt_quantity(
                h5file['/SO_Mass_500_rhocrit'][0] * 1.e10, unyt.Solar_Mass)

        data = sw.load(highz_snap)
        bh_positions = data.black_holes.coordinates.to_physical()
        bh_coordX = bh_positions[:, 0] - XPotMin
        bh_coordY = bh_positions[:, 1] - YPotMin
        bh_coordZ = bh_positions[:, 2] - ZPotMin
        bh_radial_distance = np.sqrt(bh_coordX**2 + bh_coordY**2 +
                                     bh_coordZ**2)
        bh_masses = data.black_holes.subgrid_masses.to_physical()

        if BH_LOCK_ID:
            central_bh_index = np.where(
                data.black_holes.particle_ids.v == central_bh_id_target.v)[0]
        else:
            central_bh_index = np.argmin(bh_radial_distance)

        central_bh['x'].append(bh_positions[central_bh_index, 0])
        central_bh['y'].append(bh_positions[central_bh_index, 1])
        central_bh['z'].append(bh_positions[central_bh_index, 2])
        central_bh['dx'].append(bh_coordX[central_bh_index])
        central_bh['dy'].append(bh_coordY[central_bh_index])
        central_bh['dz'].append(bh_coordZ[central_bh_index])
        central_bh['dr'].append(bh_radial_distance[central_bh_index])
        central_bh['mass'].append(bh_masses[central_bh_index])
        central_bh['m500c'].append(M500c)
        central_bh['id'].append(
            data.black_holes.particle_ids[central_bh_index])
        central_bh['redshift'].append(data.metadata.redshift)
        central_bh['time'].append(data.metadata.time)

    if INCLUDE_SNIPS:
        unitLength = data.metadata.units.length
        unitMass = data.metadata.units.mass
        unitTime = data.metadata.units.time

        snip_handles = get_snip_handles(path_to_snap, z_max=z_clip)
        for snip_handle in tqdm(snip_handles,
                                desc=f"Analysing snipshots",
                                disable=SILENT_PROGRESSBAR):

            # Open the snipshot file from the I/O stream.
            # Cannot use Swiftsimio, since it automatically looks for PartType0,
            # which is not included in snipshot outputs.
            with h5py.File(snip_handle, 'r') as f:
                bh_positions = f['/PartType5/Coordinates'][...]
                bh_masses = f['/PartType5/SubgridMasses'][...]
                bh_ids = f['/PartType5/ParticleIDs'][...]
                redshift = f['Header'].attrs['Redshift'][0]
                time = f['Header'].attrs['Time'][0]
                a = f['Header'].attrs['Scale-factor'][0]

            if BH_LOCK_ID:
                central_bh_index = np.where(
                    bh_ids == central_bh_id_target.v)[0]
            else:
                raise ValueError((
                    "Trying to lock the central BH to the halo centre of potential "
                    "in snipshots, which do not have corresponding halo catalogues. "
                    "Please, lock the central BH to the particle ID found at z = 0. "
                    "While this set-up is not yet implemented, it would be possible to "
                    "interpolate the position of the CoP between snapshots w.r.t. cosmic time."
                ))

            # This time we need to manually convert to physical coordinates and assign units.
            # Catalogue-dependent quantities are not appended.
            central_bh['x'].append(
                unyt.unyt_quantity(bh_positions[central_bh_index, 0] / a,
                                   unitLength))
            central_bh['y'].append(
                unyt.unyt_quantity(bh_positions[central_bh_index, 1] / a,
                                   unitLength))
            central_bh['z'].append(
                unyt.unyt_quantity(bh_positions[central_bh_index, 2] / a,
                                   unitLength))
            # central_bh['dx'].append(np.nan)
            # central_bh['dy'].append(np.nan)
            # central_bh['dz'].append(np.nan)
            # central_bh['dr'].append(np.nan)
            central_bh['mass'].append(
                unyt.unyt_quantity(bh_masses[central_bh_index], unitMass))
            # central_bh['m500c'].append(np.nan)
            central_bh['id'].append(
                unyt.unyt_quantity(bh_ids[central_bh_index],
                                   unyt.dimensionless))
            central_bh['redshift'].append(
                unyt.unyt_quantity(redshift, unyt.dimensionless))
            central_bh['time'].append(unyt.unyt_quantity(time, unitTime))

    # Convert lists to Swiftsimio cosmo arrays
    for key in central_bh:
        central_bh[key] = sw.cosmo_array(central_bh[key]).flatten()
        if not SILENT_PROGRESSBAR:
            print(
                f"Central BH memory [{key}]: {central_bh[key].nbytes / 1024:.3f} kB"
            )

    return central_bh
Beispiel #28
0
import swiftsimio as sw
from swiftsimio.visualisation.projection import project_gas
import argparse
from matplotlib.pyplot import imsave
from matplotlib.colors import LogNorm

parser = argparse.ArgumentParser()
parser.add_argument('-i', '--ic-file', type=str, required=True)
parser.add_argument('-t', '--top-cells-per-tile', type=int, default=3, required=False)
parser.add_argument('-o', '--outdir', type=str, default='.', required=False)
args = parser.parse_args()


mask = sw.mask(args.ic_file)
boxsize = mask.metadata.boxsize
load_region = [
    [0. * boxsize[0], 1. * boxsize[0]],
    [0. * boxsize[1], 1. * boxsize[1]],
    [0. * boxsize[2], 0.1 * boxsize[2]],
]

mask.constrain_spatial(load_region)
data = sw.load(args.ic_file, mask=mask)

mass_map = project_gas(
    data,
    resolution=1024,
    project="densities",
    parallel=True,
    backend="subsampled"
)
Beispiel #29
0
def process_single_halo(
        path_to_snap: str,
        path_to_catalogue: str,
        hse_dataset: pd.Series = None,
) -> Tuple[unyt.unyt_quantity]:
    # Read in halo properties
    with h5.File(path_to_catalogue, 'r') as h5file:
        scale_factor = float(h5file['/SimulationInfo'].attrs['ScaleFactor'])
        M500c = unyt.unyt_quantity(h5file['/SO_Mass_500_rhocrit'][0] * 1.e10, unyt.Solar_Mass)
        R500c = unyt.unyt_quantity(h5file['/SO_R_500_rhocrit'][0], unyt.Mpc) / scale_factor
        XPotMin = unyt.unyt_quantity(h5file['/Xcminpot'][0], unyt.Mpc) / scale_factor
        YPotMin = unyt.unyt_quantity(h5file['/Ycminpot'][0], unyt.Mpc) / scale_factor
        ZPotMin = unyt.unyt_quantity(h5file['/Zcminpot'][0], unyt.Mpc) / scale_factor

        # If no custom aperture, select r500c as default
        if hse_dataset is not None:
            assert R500c.units == hse_dataset["R500hse"].units
            assert M500c.units == hse_dataset["M500hse"].units
            R500c = hse_dataset["R500hse"]
            M500c = hse_dataset["M500hse"]

    # Read in gas particles to compute the core-excised temperature
    mask = sw.mask(path_to_snap, spatial_only=False)
    region = [[XPotMin - 1.1 * R500c, XPotMin + 1.1 * R500c],
              [YPotMin - 1.1 * R500c, YPotMin + 1.1 * R500c],
              [ZPotMin - 1.1 * R500c, ZPotMin + 1.1 * R500c]]
    mask.constrain_spatial(region)
    mask.constrain_mask(
        "gas", "temperatures",
        Tcut_halogas * mask.units.temperature,
        1.e12 * mask.units.temperature
    )
    data = sw.load(path_to_snap, mask=mask)
    posGas = data.gas.coordinates
    massGas = data.gas.masses
    velGas = data.gas.velocities
    mass_weighted_temperatures = data.gas.temperatures * data.gas.masses

    # Select hot gas within sphere and without core
    deltaX = posGas[:, 0] - XPotMin
    deltaY = posGas[:, 1] - YPotMin
    deltaZ = posGas[:, 2] - ZPotMin
    deltaR = np.sqrt(deltaX ** 2 + deltaY ** 2 + deltaZ ** 2)

    # Count only particles inside R500crit
    index = np.where(deltaR < R500c)[0]

    # Compute kinetic energy in the halo's rest frame
    peculiar_velocity = np.sum(velGas[index] * massGas[index, None], axis=0) / np.sum(massGas[index])
    velGas[:, 0] -= peculiar_velocity[0]
    velGas[:, 1] -= peculiar_velocity[1]
    velGas[:, 2] -= peculiar_velocity[2]

    Ekin = np.sum(
        0.5 * massGas[index] * (velGas[index, 0] ** 2 + velGas[index, 1] ** 2 + velGas[index, 2] ** 2)
    ).to("1.e10*Mpc**2*Msun/Gyr**2")
    Etherm = np.sum(
        1.5 * unyt.boltzmann_constant * mass_weighted_temperatures[index] / (unyt.hydrogen_mass / 1.16)
    ).to("1.e10*Mpc**2*Msun/Gyr**2")

    return M500c, Ekin, Etherm
Beispiel #30
0
def create_single_particle_dataset(filename: str, output_name: str):
    """
    Create an hdf5 snapshot with two particles at an identical location

    Parameters
    ----------
    filename: str
        name of file from which to copy metadata
    output_name: str
        name of single particle snapshot
    """
    # Create a dummy mask in order to write metadata
    data_mask = mask(filename)
    boxsize = data_mask.metadata.boxsize
    region = [[0, b] for b in boxsize]
    data_mask.constrain_spatial(region)

    # Write the metadata
    infile = h5py.File(filename, "r")
    outfile = h5py.File(output_name, "w")
    list_of_links, _ = find_links(infile)
    write_metadata(infile, outfile, list_of_links, data_mask)

    # Write a single particle
    particle_coords = cosmo_array([[1, 1, 1], [1, 1, 1]],
                                  data_mask.metadata.units.length)
    particle_masses = cosmo_array([1, 1], data_mask.metadata.units.mass)
    mean_h = mean(infile["/PartType0/SmoothingLengths"])
    particle_h = cosmo_array([mean_h, mean_h], data_mask.metadata.units.length)
    particle_ids = [1, 2]

    coords = outfile.create_dataset("/PartType0/Coordinates",
                                    data=particle_coords)
    for name, value in infile["/PartType0/Coordinates"].attrs.items():
        coords.attrs.create(name, value)

    masses = outfile.create_dataset("/PartType0/Masses", data=particle_masses)
    for name, value in infile["/PartType0/Masses"].attrs.items():
        masses.attrs.create(name, value)

    h = outfile.create_dataset("/PartType0/SmoothingLengths", data=particle_h)
    for name, value in infile["/PartType0/SmoothingLengths"].attrs.items():
        h.attrs.create(name, value)

    ids = outfile.create_dataset("/PartType0/ParticleIDs", data=particle_ids)
    for name, value in infile["/PartType0/ParticleIDs"].attrs.items():
        ids.attrs.create(name, value)

    # Get rid of all traces of DM
    del outfile["/Cells/Counts/PartType1"]
    del outfile["/Cells/Offsets/PartType1"]
    nparts_total = [2, 0, 0, 0, 0, 0]
    nparts_this_file = [2, 0, 0, 0, 0, 0]
    outfile["/Header"].attrs["NumPart_Total"] = nparts_total
    outfile["/Header"].attrs["NumPart_ThisFile"] = nparts_this_file

    # Tidy up
    infile.close()
    outfile.close()

    return