Beispiel #1
0
    def __init__(self, args):
        """Load star data of entire simulation."""

        # Get birth time and mass of all stars in this halo.
        vr_snap = hd.read_attribute(f'{args.wdir}{args.bh_file}', 'Haloes',
                                    'VR_Snapshot')
        if vr_snap is None:
            self.is_set_up = False
            return

        snap_file = f'{args.wdir}{args.snap_name}_{vr_snap:04d}.hdf5'
        stellar_aexp = hd.read_data(snap_file, 'PartType4/BirthScaleFactors')
        self.birth_times = aexp_to_time(stellar_aexp)
        self.mass = hd.read_data(snap_file, 'PartType4/InitialMasses') * 1e10
        self.ids = hd.read_data(snap_file, 'PartType4/ParticleIDs')
        coordinates = hd.read_data(snap_file, 'PartType4/Coordinates')
        coordinates *= (hd.read_attribute(snap_file, 'Header',
                                          'Scale-factor')[0])

        # Look up star IDs in VR
        vr_file = f'{args.wdir}{args.vr_file}_{vr_snap:04d}'
        self.haloes, vr_zred, vr_aexp = xl.connect_ids_to_vr(self.ids,
                                                             vr_file,
                                                             require=True)

        # Get radii of stars
        halo_centres = hd.read_data(f'{vr_file}.hdf5',
                                    'MinimumPotential/Coordinates')
        self.radii = np.linalg.norm(coordinates - halo_centres[self.haloes, :],
                                    axis=1) * 1e3
        ind_not_in_halo = np.nonzero(self.haloes < 0)[0]
        self.radii[ind_not_in_halo] = -1

        self.is_set_up = True
Beispiel #2
0
    def __init__(self, vr_file, sim_type='Hydro'):
        self.vr_file = f'{vr_file}.hdf5'
        self.vr_part_file = f'{vr_file}_particles.hdf5'

        self.ids = hd.read_data(self.vr_part_file, 'Haloes/IDs')
        if sim_type.lower().startswith('dm'):
            self.ids *= 2
    
        self.ptypes = hd.read_data(self.vr_part_file, 'Haloes/PartTypes')
        self.offsets = hd.read_data(self.vr_part_file, 'Haloes/Offsets')

        self.n_haloes = len(self.offsets) - 1
def process_sim(isim, args):
    """Process one individual simulation"""

    if isinstance(isim, int):
        wdirs = glob.glob(f'{args.basedir}/ID{isim}*/')
        if len(wdirs) != 1:
            set_trace()
        wdir = wdirs[0]
    else:
        wdir = args.basedir + '/' + isim + '/'
        print(f"Analysing simulation {wdir}...")

    bfile = wdir + args.catloc
    if not os.path.isfile(bfile):
        print(f"Could not find BH data file for simulation {isim}.")
        return

    # Copy out the desired fields
    for field in args.bh_fields:
        data = hd.read_data(bfile, field)
        if data is None:
            print(f"Could not load data set '{field}'!")
            set_trace()

        hd.write_data(args.outfile, f'ID{isim}/{field}', data)

    # Copy out metadata fields
    times = hd.read_data(bfile, 'Times')
    redshifts = hd.read_data(bfile, 'Redshifts')
    first_indices = hd.read_data(bfile, 'FirstIndices')
    vr_haloes = hd.read_data(bfile, 'Haloes')
    vr_halo_mstar = hd.read_data(bfile, 'Halo_MStar')
    vr_halo_sfr = hd.read_data(bfile, 'Halo_SFR')
    vr_halo_m200c = hd.read_data(bfile, 'Halo_M200c')
    vr_halo_types = hd.read_data(bfile, 'HaloTypes')
    vr_halo_flag = hd.read_data(bfile, 'Flag_MostMassiveInHalo')

    hd.write_data(args.outfile, f'ID{isim}/Times', times)
    hd.write_data(args.outfile, f'ID{isim}/Redshifts', redshifts)
    hd.write_data(args.outfile, f'ID{isim}/FirstIndices', first_indices)

    if vr_haloes is not None:
        hd.write_data(args.outfile, f'ID{isim}/Haloes', vr_haloes)
        hd.write_data(args.outfile, f'ID{isim}/Halo_MStar', vr_halo_mstar)
        hd.write_data(args.outfile, f'ID{isim}/Halo_SFR', vr_halo_sfr)
        hd.write_data(args.outfile, f'ID{isim}/Halo_M200c', vr_halo_m200c)
        hd.write_data(args.outfile, f'ID{isim}/Halo_Types', vr_halo_types)
        hd.write_data(args.outfile, f'ID{isim}/Halo_FlagMostMassiveBH',
                      vr_halo_flag)
Beispiel #4
0
def connect_ids_to_vr(ids, vr_file, require=False):
    """Core function to find the VR halo of a set of IDs."""

    num_ids = len(ids)
    
    vr_particle_file = f'{vr_file}_particles.hdf5'
    vr_main_file = f'{vr_file}.hdf5'
    
    # Abort if VR catalogue could not be found
    if ((not os.path.isfile(vr_particle_file)) or
        (not os.path.isfile(vr_main_file))):
        print(f"VR catalogue {vr_file} does not exist...")
        if require:
            set_trace()
        return None

    # Find redshift of VR catalogue
    aexp = float(hd.read_attribute(vr_main_file, 'SimulationInfo',
                                   'ScaleFactor'))
    zred = 1/aexp - 1
    print(f"Connecting to VR catalogue {vr_file} at redshift {zred}...")
    
    # Load VR particle IDs
    vr_ids = hd.read_data(vr_particle_file, 'Haloes/IDs')
    vr_offsets = hd.read_data(vr_particle_file, 'Haloes/Offsets')

    # Locate 'our' IDss in the VR ID list
    print("Locating IDs in VR list...")
    stime = time.time()
    ind_in_vr, found_in_vr = hx.find_id_indices(ids, vr_ids)
    print(f"... took {(time.time() - stime):.3f} sec., located "
          f"{len(found_in_vr)} "
          f"/ {num_ids} IDs in VR list ({len(found_in_vr)/num_ids*100:.3f}%).")
    
    # Now convert VR particle indices to halo indices
    halo_guess = np.searchsorted(vr_offsets, ind_in_vr[found_in_vr],
                                 side='right') - 1
    ind_good = np.nonzero(ind_in_vr[found_in_vr] < vr_offsets[halo_guess+1])[0]

    vr_halo = np.zeros(num_ids, dtype=int) - 1
    vr_halo[found_in_vr[ind_good]] = halo_guess[ind_good]
    print(f"... could match {len(ind_good)} / {num_ids} IDs to haloes. "
          f"({len(ind_good)/num_ids*100:.3f}%).")

    return vr_halo, aexp, zred
Beispiel #5
0
def add_eagle(ax, eagle_name, aexp, linestyle='-', legend_y=None):
    """Add distribution from an EAGLE simulation."""

    eagle_aexps = hd.read_data('./comparison_data/EAGLE_birth_densities.hdf5',
                               f'{eagle_name}/ScaleFactors')
    isnap_eagle = np.argmin(np.abs(aexp - eagle_aexps))
    print(f"Adding EAGLE snap {isnap_eagle}...")
    eagle_x = hd.read_data('./comparison_data/EAGLE_birth_densities.hdf5',
                           f'{eagle_name}/S{isnap_eagle}/nH_by_nCrit')
    eagle_y = hd.read_data('./comparison_data/EAGLE_birth_densities.hdf5',
                           f'{eagle_name}/S{isnap_eagle}/CumulativeMassFraction')

    plt.plot(eagle_x, eagle_y, color='grey', linestyle=linestyle)

    if legend_y is not None:
        xl.legend_item(ax, [0.03, 0.1], legend_y,
                       eagle_name.replace('_', '\_'), color='grey',
                       ls=linestyle, fontsize=9)
Beispiel #6
0
def get_birth_densities(args, isnap):
    """Get the birth densities of all stars in a given snapshot."""
    snap_file = f'{args.wdir}{args.snap_name}_{isnap:04d}.hdf5'
    birth_densities = hd.read_data(snap_file, 'PartType4/BirthDensities')
    masses = hd.read_data(snap_file, 'PartType4/InitialMasses')
    
    # Get conversion factor to n_H [cm^-3]
    m_p = 1.673e-24  # Proton mass in g
    X_H = 0.752 # Hydrogen mass fraction (primordial)
    rho_to_cgs_factor = hd.read_attribute(
        snap_file, 'PartType4/BirthDensities',
        'Conversion factor to physical CGS (including cosmological '
        'corrections)')
    rho_to_nH_cgs = (X_H / m_p) * (rho_to_cgs_factor)
        
    # Convert the densities to the "critical density" (see Crain+15)
    n_crit = 10.0 * (1.81)**(-1/2)  # in n_H [cm^-3]

    return np.log10(birth_densities * rho_to_nH_cgs / n_crit), masses
Beispiel #7
0
def plot_sfr(loc,
             color,
             label,
             simtype='Swift',
             boxsize=25,
             linewidth=1.0,
             linestyle='-',
             alpha=1.0):

    print(f"Plotting SFR for {label}...")
    if not os.path.exists(loc):
        print(f"Could not find '{loc}'!")
        return

    if simtype == 'Swift':
        ls = '-'
    else:
        ls = ':'

    if loc.endswith('.hdf5'):
        aexp = hd.read_data(loc, 'aExp')
        sfr = hd.read_data(loc, 'SFR') / (boxsize**3)
    elif loc.endswith('.txt'):
        sfrdata = ascii.read(loc)
        if simtype == 'Swift':
            aexp = np.array(sfrdata['col3'])
            sfr = np.array(sfrdata['col8']) / (boxsize**3) * 1.022690e-2
        else:
            aexp = np.array(sfrdata['col1'])
            sfr = np.array(sfrdata['col3']) / (boxsize**3)
    else:
        print(f'Incompatible file ending "{loc}"')
        set_trace()

    ind_plot = np.linspace(0, len(aexp), 2000, endpoint=False).astype(int)
    plt.plot(aexp[ind_plot],
             sfr[ind_plot],
             color=color,
             linewidth=linewidth,
             label=label,
             linestyle=linestyle,
             alpha=alpha)
    print("... done!")
Beispiel #8
0
def write_vr_plot(writer, ibh, isnap, bh_list, plotdata_file, iiplot, iplot,
                  plot_prefix):
    """Write one single VR plot."""

    plotloc = (f'{plot_prefix}_{iplot[0]}-{iplot[1]}-{iplot[2]}_'
               f'BH-{ibh}_snap-{isnap}.png')

    writer.write(f'<img src="{plotloc}" alt="{iplot[0]}-{iplot[1]}-'
                 f'{iplot[2]}" ')
    if os.path.isfile(plotdata_file):
        writer.write(f'usemap="#map-{iplot[0]}-{iplot[1]}" ')
    writer.write(f'width=450>\n')

    # Interactive map is only possible if we have the data file
    if not os.path.isfile(plotdata_file):
        return

    writer.write('\n')
    writer.write(f'<map name="map-{iplot[0]}-{iplot[1]}">')
    writer.write('\n')

    imx = hd.read_data(plotdata_file, f'S{isnap}/{iplot[0]}-{iplot[1]}/xpt')
    imy = hd.read_data(plotdata_file, f'S{isnap}/{iplot[0]}-{iplot[1]}/ypt')

    # Add a link for each individual BH to the map...
    for ixbh, xbh in enumerate(bh_list):

        if imx[ixbh] * 0 != 0 or imy[ixbh] * 0 != 0:
            continue

        x_this = int(imx[ixbh] * 450)
        y_this = int(imy[ixbh] * 450)
        rad = 5

        writer.write(f'<area shape="circle" coords="{x_this}, {y_this}, '
                     f'{rad}" alt="BH {xbh}" href="index_bh-{xbh}_'
                     f'snap-{isnap}.html">\n')

    writer.write(f'</map>\n')
Beispiel #9
0
def get_star_haloes(args, isnap):
    """Get the halo ID of all stars in a given snapshot."""
    snap_file = f'{args.wdir}{args.snap_name}_{isnap:04d}.hdf5'
    star_ids = hd.read_data(snap_file, 'PartType4/ParticleIDs')
    star_coordinates = hd.read_data(snap_file, 'PartType4/Coordinates')
    star_coordinates *= (
        hd.read_attribute(snap_file, 'Header', 'Scale-factor')[0])
    
    vr_file = f'{args.wdir}{args.vr_name}_{isnap:04d}'
    star_haloes, aexp, zred = (
        xl.connect_ids_to_vr(star_ids, vr_file, require=True))

    # Get radii of stars
    halo_centres = hd.read_data(f'{vr_file}.hdf5',
                                'MinimumPotential/Coordinates')
    star_radii = np.linalg.norm(star_coordinates
                                - halo_centres[star_haloes, :], axis=1) * 1e3
    ind_not_in_halo = np.nonzero(star_haloes < 0)[0]
    star_radii[ind_not_in_halo] = -1
    
    args.aexp = aexp
    args.zred = zred
    return star_haloes, star_radii, aexp
Beispiel #10
0
def add_coda_to_offsets(vr_part_file):
    """Add the coda to particle offsets."""

    num_name = {
        'Haloes': 'NumberOfBoundParticles_Total',
        'Unbound': 'NumberOfUnboundParticles_Total',
        'Groups': 'NumberOfSOParticles_Total'
    }

    for grp in ['Haloes', 'Unbound', 'Groups']:
        offsets = read_data(vr_part_file, f'{grp}/Offsets')
        num_ids = read_attribute(vr_part_file, 'Header', num_name[grp])

        offsets = np.concatenate((offsets, [num_ids]))
        write_data(vr_part_file, f'{grp}/Offsets', offsets)
Beispiel #11
0
def extract_bh_data(snapfile, isim, isnap, args):
    """Extract BH data from one particular file"""

    if isinstance(isim, int):
        pre = f'ID{isim}/S{isnap}/BH/'
    else:
        pre = f'{isim}/S{isnap}/BH/'

    zred = hd.read_attribute(snapfile, 'Header', 'Redshift')[0]
    hd.write_attribute(args.outfile, pre, 'Redshift', zred)
    
    for field in args.bh_fields:
        data = hd.read_data(snapfile, 'PartType5/' + field)
        if data is not None:
            hd.write_data(args.outfile, pre + '/' + field, data)
Beispiel #12
0
def extract_vr_data(vrfile, isim, isnap, args):
    """Extract VR data from one particular file"""

    if isinstance(isim, int):
        pre = f'ID{isim}/S{isnap}/'
    else:
        pre = f'{isim}/S{isnap}/'
        
    for field in args.vr_fields:
        data = hd.read_data(vrfile, field[0])
        if data is None:
            print(f"Could not find field '{field[0]}' in VR file '{vrfile}'!")
            #set_trace()

        else:
            hd.write_data(args.outfile, f'{pre}/{field[1]}', data)
Beispiel #13
0
def setup_output(args):
    """Set up a dict of arrays to hold the various black hole data."""

    # Get the names of all existing BH data sets
    snapfile = args.wdir + args.snap_name + f'_{args.first_snap:04d}.hdf5'
    bh_datasets = hd.list_datasets(snapfile, 'PartType5')
    print(f"There are {len(bh_datasets)} BH data sets...")

    # Starting from empty dict, add one array for each data set (except IDs)
    output_dict = {}
    comment_dict = {}

    for dset in bh_datasets:

        # We don't need to load particle IDs, have these already
        if dset == 'ParticleIDs':
            continue

        if args.include is not None and dset not in args.include:
            continue
        if args.exclude is not None and dset in args.exclude:
            continue

        # For simplicity, read the data set in to get its shape/type
        data = hd.read_data(snapfile, f'PartType5/{dset}')
        comment = hd.read_attribute(snapfile, f'PartType5/{dset}',
                                    'Description')
        if data is None:
            print(f"Strange -- could not read BH data set {dset}?!")
            set_trace()
        outshape = list(data.shape)
        outshape[0] = args.num_bhs
        outshape.append(args.num_bh_snaps)
        array = np.zeros(tuple(outshape), data.dtype) + np.nan

        # Add array to overall dict
        output_dict[dset] = array
        comment_dict[dset] = comment

    print("... finished creating output arrays.")

    args.times = np.zeros(args.num_bh_snaps)
    args.redshifts = np.zeros(args.num_bh_snaps)

    return output_dict, comment_dict
Beispiel #14
0
def process_snapshot(isim, isnap, args):
    """Process one snapshot."""
    print("")
    print(f"Processing snapshot {isnap}...")
    
    snap_this = Snapshot(f'{args.wdir}{args.vr_name}_{isnap:04d}')
    snap_dmo = Snapshot(f'{args.dmo_dir}{args.vr_name}_{isnap:04d}',
                        sim_type='dm-only')

    # Find the best match in the DMO snapshot for each halo in this sim
    print("\nMatching from this to DMO...")
    match_in_dmo, gate_this_dmo, match_frac_in_dmo = (
        match_haloes(snap_this, snap_dmo))

    # ... and do the same in reverse
    print("\nMatching from DMO to this...")
    match_in_this, gate_dmo_this, match_frac_in_this = (
        match_haloes(snap_dmo, snap_this))

    # Reject matches that are not bijective
    ind_non_bijective = np.nonzero((match_in_dmo < 0) |
                                   (match_in_this[match_in_dmo] !=
                                    np.arange(snap_this.n_haloes)))[0]
    match_in_dmo[ind_non_bijective] = -1
    ind_not_matched = np.nonzero(match_in_dmo < 0)[0]
    
    # Write out results
    vr_file_this = f'{args.wdir}{args.vr_name}_{isnap:04d}.hdf5'
    vr_file_dmo = f'{args.dmo_dir}{args.vr_name}_{isnap:04d}.hdf5'

    hd.write_data(vr_file_this, 'MatchInDMO/Haloes', match_in_dmo)
    hd.write_data(vr_file_this, 'MatchInDMO/MatchFractionInDMO',
                  match_frac_in_dmo)
    match_frac_in_this_aligned = match_frac_in_this[match_in_dmo]
    match_frac_in_this_aligned[ind_not_matched] = np.nan
    hd.write_data(vr_file_this, 'MatchInDMO/MatchFractionInThis',
                  match_frac_in_this_aligned)
    
    for iset in ['M200crit', 'Masses', 'MaximumCircularVelocities']:
        data_dmo = hd.read_data(vr_file_dmo, iset)
        data_dmo_aligned = data_dmo[match_in_dmo]
        data_dmo_aligned[ind_not_matched] = np.nan
        
        hd.write_data(vr_file_this, f'MatchInDMO/{iset}', data_dmo_aligned)
Beispiel #15
0
def process_output(iisnap,
                   isnap,
                   output_dict,
                   bpart_ids,
                   args,
                   bpart_rev_ids=None):
    """Transcribe black hole data from one simulation output file.

    Parameters:
    -----------
    iisnap : int
        Index of currently processed output in collective array.
    isnap : int
        Simulation index of currently processed output.
    output_dict : dict of ndarrays
        Dictionary containing arrays to be filled with data.
    bpart_ids : ndarray
        The IDs of black holes to fill into output lists.
    args : dict of values
        Configuration parameters.
    rev : bool, optional
        If True, assume that bpart_ids is actually the reverse list of
        BH IDs.
    """
    if iisnap % 50 == 0:
        print(f"Transcribing BH data for snapshot {isnap}...")
        stime = time.time()

    snapfile = args.wdir + args.snap_name + f'_{isnap:04d}.hdf5'

    # Get the names of all data sets to transcribe
    dataset_list = list(output_dict.keys())

    cstime = time.time()
    # Load IDs of particles in current output snapshot:
    bpart_ids_curr = hd.read_data(snapfile, 'PartType5/ParticleIDs')

    # Convert them to 'Black-IDs', i.e. their index in the output list
    if bpart_rev_ids is not None:
        rstime = time.time()
        bh_ids = bpart_rev_ids.query(bpart_ids_curr)
        #print(f"Querying {isnap} took {(time.time()-rstime):.3f} sec.")
        ind_matched = np.nonzero(bh_ids >= 0)[0]
        rstime = time.time()
        #print(f"Checking {isnap} took {(time.time()-rstime):.3f} sec.")
    else:
        fstime = time.time()
        bh_ids, ind_matched = hx.find_id_indices(bpart_ids_curr, bpart_ids)
        print(f"FII {isnap} took {(time.time()-fstime):.3f} sec.")

    if len(ind_matched) != len(bpart_ids_curr):
        print(f"Why can't we match all BHs from output {isnap}?!?")
        set_trace()
    cetime = time.time()

    if iisnap % 50 == 0:
        print(f"... lookup took {cetime - cstime:.3f} sec.")

    # Load the time and redshift of current output
    redshift = hd.read_attribute(snapfile, 'Header', 'Redshift')[0]
    sim_time = hd.read_attribute(snapfile, 'Header', 'Time')[0]
    utime = hd.read_attribute(snapfile, 'Units', 'Unit time in cgs (U_t)')[0]
    utime /= (3600.0 * 24 * 365.24 * 1e9)  # Convert from sec to Gyr
    sim_time *= utime

    args.times[iisnap] = sim_time
    args.redshifts[iisnap] = redshift

    # Go through all to-transcribe data sets and copy them out
    for dset in dataset_list:

        # Make sure that the output data set has the expected shape
        if output_dict[dset].shape[0] != len(bpart_ids):
            print(f"Inconsistent shape of BH output array '{dset}'.")
            set_trace()

        # Load the data, make sure this actually worked
        data = hd.read_data(snapfile, 'PartType5/' + dset)
        if data is None:
            print(f"Oh my goodness, why can we now not find data set "
                  f"'{dset}' for black holes in output {isnap}?")
            set_trace()

        output_dict[dset][bh_ids, ..., iisnap] = data

    if iisnap % 50 == 0:
        print(f"... finished in {time.time() - stime:.3f} sec.")
Beispiel #16
0
def main():

    print("Parsing input arguments...")
    parser = argparse.ArgumentParser(description="Parse input parameters.")
    parser.add_argument('sim', help='Simulation index or name to analyse')
    parser.add_argument('--base_dir',
                        help=f'Simulation base directory, default: '
                        f'{local.BASE_DIR}',
                        default=local.BASE_DIR)
    parser.add_argument('--bh_mmax',
                        type=float,
                        help='Maximum BH mass, for the scaling in the images '
                        'in log (M/M_Sun), default: 8.5.',
                        default=8.5)
    parser.add_argument('--numpix',
                        type=int,
                        help='Size of images in pixels, default: 1000',
                        default=1000)
    parser.add_argument('--gallery_dir', default='gallery')
    parser.add_argument('--plot_prefix',
                        default='vr_plots',
                        help='Prefix for plot files (within gallery_dir), '
                        'default: vr_plots')
    parser.add_argument('--snapshots',
                        nargs='+',
                        type=int,
                        help='Snapshots for which to set up websites.')
    parser.add_argument('--snap_name',
                        default='snapshot',
                        help='Snapshot prefix, default: "snapshot".')
    parser.add_argument('--mstar_min',
                        type=float,
                        help='Minimum stellar mass of the host galaxy for '
                        'a black hole to be included, in M_Sun '
                        '(default: 3e10)',
                        default=3e10)
    parser.add_argument('--m200_min',
                        type=float,
                        help='Minimum M200c of the host galaxy for '
                        'a black hole to be included, in M_Sun '
                        '(default: 0, i.e. select on stellar mass only)',
                        default=0.0)
    parser.add_argument('--bh_data_file',
                        default='black_hole_data.hdf5',
                        help='Name of the file containing the BH data, '
                        'default: "black_hole_data.hdf5"')
    parser.add_argument('--vr_prefix',
                        default='vr',
                        help='Prefix of (combined) VR catalogue, default: vr.')
    parser.add_argument('--snap_frontpage',
                        type=int,
                        help='Snapshot for images on frontpage (default: 36)',
                        default=36)
    parser.add_argument('--size_frontpage',
                        type=float,
                        help='Size of image for front page, in pMpc '
                        '(default: 0.03)',
                        default=0.03)

    args = parser.parse_args()

    if len(args.snapshots) == 0:
        print("No snapshots selected, aborting.")

    # Adjust selected front page size to closest match in list
    frontpage_sizeind = np.argmin(
        np.abs(np.array(ap_list) - args.size_frontpage))
    args.size_frontpage = ap_list[frontpage_sizeind]

    # Construct the full working directory of the simulation
    args.wdir = xl.get_sim_dir(args.base_dir, args.sim)

    # Look up the snapshot redshifts
    get_snapshot_redshifts(args)

    args.plotdata_file = (f'{args.wdir}{args.gallery_dir}/'
                          f'{args.plot_prefix}.hdf5')
    if os.path.isfile(args.plotdata_file):
        bh_list = hd.read_data(args.plotdata_file, 'BlackHoleBIDs')
        select_list = None
        print(f"Using pre-created BH list with {len(bh_list)} BHs.")

    else:
        # Find BHs we are intereste in, load data
        select_list = [["Halo_M200c", '>=', args.m200_min],
                       ["Halo_MStar", '>=', args.mstar_min],
                       ["Flag_MostMassiveInHalo", '==', 1],
                       ["HaloTypes", '==', 10]]
        bh_list = None

    bh_data, bh_sel = xl.lookup_bh_data(args.wdir + args.bh_data_file,
                                        bh_props_list, select_list)
    if bh_list is None:
        bh_list = bh_sel

    if len(bh_list) == 0:
        print("No black holes selected, aborting.")
        return

    # Generate the script to auto-generate all the required images
    generate_image_script(args, bh_list)

    # Generate the script to auto-generate all the tracks
    generate_track_script(args, bh_list)

    generate_website(args, bh_data, bh_list)
Beispiel #17
0
def process_sim(args, isim, have_full_sim_dir):
    """Generate the images for one particular simulation."""

    if have_full_sim_dir:
        args.wdir = isim
    else:
        args.wdir = xl.get_sim_dir(args.base_dir, isim)

    # Name of the input BH data catalogue
    args.catloc = f'{args.wdir}{args.bh_file}'

    # Find BHs we are intereste in, load data (of internal VR match)
    select_list = [["Halo_MStar", '>=', args.halo_mstar_range[0]],
                   ["Halo_MStar", '<', args.halo_mstar_range[1]],
                   ["DMO_M200c", '>=', args.halo_m200_range[0]],
                   ["DMO_M200c", '<', args.halo_m200_range[1]]]
    if not args.include_subdominant_bhs:
        select_list.append(['Flag_MostMassiveInHalo', '==', 1])
    if not args.include_satellites:
        select_list.append(['HaloTypes', '==', 10])

    if args.bh_mass_range is not None:
        zreds = hd.read_data(args.wdir + args.bh_file, 'Redshifts')
        best_index = np.argmin(np.abs(zreds - args.bh_selection_redshift))
        print(f"Best index for redshift {args.bh_selection_redshift} is "
              f"{best_index}.")

        # Subgrid masses are in 10^10 M_sun, so need to adjust selection range
        select_list.append(
            ['SubgridMasses', '>=', args.bh_mass_range[0] / 1e10, best_index])
        select_list.append(
            ['SubgridMasses', '<=', args.bh_mass_range[1] / 1e10, best_index])

    bh_props_list = ['SubgridMasses', 'Redshifts', 'ParticleIDs']
    bh_data, bh_list = xl.lookup_bh_data(args.wdir + args.bh_file,
                                         bh_props_list, select_list)

    if len(bh_list) == 0:
        print("No BHs selected, aborting.")
        return

    args.sim_pointdata_loc = args.wdir + args.plot_prefix + '.hdf5'
    if not os.path.isdir(os.path.dirname(args.sim_pointdata_loc)):
        os.makedirs(os.path.dirname(args.sim_pointdata_loc))
    if os.path.isfile(args.sim_pointdata_loc):
        shutil.move(args.sim_pointdata_loc, args.sim_pointdata_loc + '.old')
    hd.write_data(args.sim_pointdata_loc,
                  'BlackHoleBIDs',
                  bh_list,
                  comment='BIDs of all BHs selected for this simulation.')

    # Go through snapshots
    for iisnap, isnap in enumerate(args.snapshots):

        print("")
        print(f"Processing snapshot {isnap}...")

        # Need to explicitly connect BHs to VR catalogue from this snap
        vr_data = xl.connect_to_galaxies(
            bh_data['ParticleIDs'][bh_list],
            f'{args.wdir}{args.vr_file}_{isnap:04d}',
            extra_props=[('ApertureMeasurements/Projection1/30kpc/'
                          'Stars/HalfMassRadii', 'StellarHalfMassRad'),
                         ('MatchInDMO/M200crit', 'DMO_M200c')])

        # Add subgrid mass of BHs themselves
        ind_bh_cat = np.argmin(
            np.abs(bh_data['Redshifts'] - vr_data['Redshift']))
        print(f"Using BH masses from index {ind_bh_cat}")
        vr_data['BH_SubgridMasses'] = (
            bh_data['SubgridMasses'][bh_list, ind_bh_cat] * 1e10)

        n_good_m200 = np.count_nonzero(vr_data['DMO_M200c'] * 0 == 0)
        print(f"Have {n_good_m200} BHs with DMO_M200c, out of "
              f"{len(bh_list)}.")

        # Make plots for each BH, and "general" overview plot
        print("Overview plot")
        generate_vr_plots(args, vr_data, isnap)

        if not args.summary_only:
            for iibh, ibh in enumerate(bh_list):
                print(f"Make plot for BH-BID {ibh} ({iibh}/{len(bh_list)})")
                generate_vr_plots(args, vr_data, isnap, iibh, ibh)

        print("Done!")
def main():

    print("Parsing input arguments...")
    parser = argparse.ArgumentParser(description="Parse input parameters.")
    parser.add_argument('sim', help='Simulation index/name to analyse')
    parser.add_argument('--bh_bid', type=int,
                        help='BID of the black hole to highlight.')
    parser.add_argument('--base_dir', default=local.BASE_DIR,
                        help='Base directory of the simulation, default: '
                             f'{local.BASE_DIR}')
    parser.add_argument('--bh_file', default='black_hole_data.hdf5',
                        help='Name of the file containing the BH data, '
                             'default: "black_hole_data.hdf5"')
    parser.add_argument('--plot_prefix', default='gallery/bh_growth_tracks',
                        help='Prefix of output files, default: '
                             '"gallery/bh_growth_tracks')
    parser.add_argument('--vrplot_prefix', default='gallery/vr_plots',
                        help='Prefix of VR plots, default: '
                             '"gallery/vr_plots".')
    parser.add_argument('--plot_mass_range', type=float, nargs='+',
                        help='Min and max mass of plot range, default: '
                             '5.0 8.5', default=[5.0, 8.5])
    parser.add_argument('--bh_mass_range', type=float, nargs='+',
                        help='Min and max BH mass for selection, at target z '
                             '[M_Sun], default: no selection.')
    parser.add_argument('--bh_selection_redshift', type=float, default=0.0,
                        help='Redshift at which to make BH mass selection '
                             '(default: 0.0)')
    parser.add_argument('--include_subdominant_bhs', action='store_true',
                        help='Only show black holes that are the most massive '
                             'in their galaxy, at the linked snapshot.')
    parser.add_argument('--halo_mstar_range', default=[3e10, 4e11], type=float,
                        help='Min and max stellar mass of host galaxy '
                             '[M_Sun], default: 3e10, 4e11.',
                        nargs='+')
    parser.add_argument('--halo_m200_range', default=[0, 1e16], type=float,
                        help='Min and max M200 mass of host galaxy '
                             '[M_Sun], default: 0, 1e16.',
                        nargs='+')

    parser.add_argument('--include_satellites', action='store_true',
                        help='Only show BHs in central haloes, not satellites.')
    parser.add_argument('--show_target_only', action='store_true',
                        help='Show only the target BH, not possible others '
                             'that also match the selection criteria.')
    parser.add_argument('--show_median', action='store_true',
                        help='Add the median for all selected BHs.')
    parser.add_argument('--summary_only', action='store_true',
                        help='Only generate summary plot, not ones for '
                             'individual BHs.')
    parser.add_argument('--alpha_others', type=float, default=0.25,
                        help='Alpha value for non-target BHs, default: 0.25')

    args = parser.parse_args()

    args.wdir = xl.get_sim_dir(args.base_dir, args.sim)

    # Name of the input catalogue, containing all the data to plot
    args.catloc = f'{args.wdir}{args.bh_file}'

    args.plotdata_file = f'{args.wdir}{args.vrplot_prefix}.hdf5'
    if os.path.isfile(args.plotdata_file):
        bh_list = hd.read_data(args.plotdata_file, 'BlackHoleBIDs')
        select_list = None
    else:
        # Find BHs we are intereste in, load data
        select_list = [
            ["Halo_MStar", '>=', args.halo_mstar_range[0]],
            ["Halo_MStar", '<', args.halo_mstar_range[1]],
            ["Halo_M200c", '>=', args.halo_m200_range[0]],
            ["Halo_M200c", '<', args.halo_m200_range[1]]
        ]
        if not args.include_subdominant_bhs:
            select_list.append(['Flag_MostMassiveInHalo', '==', 1])
        if not args.include_satellites:
            select_list.append(['HaloTypes', '==', 10])

        if args.bh_mass_range is not None:
            zreds = hd.read_data(args.wdir + args.bh_file, 'Redshifts')
            best_index = np.argmin(np.abs(zreds - args.bh_selection_redshift))
            print(f"Best index for redshift {args.bh_selection_redshift} is "
                f"{best_index}.")
        
            # Subgrid masses are in 10^10 M_sun, so need to adjust selection range
            select_list.append(
                ['SubgridMasses', '>=', args.bh_mass_range[0]/1e10, best_index])
            select_list.append(
                ['SubgridMasses', '<=', args.bh_mass_range[1]/1e10, best_index])        

        bh_list = None
            
    bh_data, bh_sel = xl.lookup_bh_data(args.wdir + args.bh_file,
                                        bh_props_list, select_list)
    if bh_list is None:
        bh_list = bh_sel
    
    generate_track_image(args, bh_data, bh_list)

    if args.bh_bid is None:
        # No target specified: process all selected BHs
        for iibh, ibh in enumerate(bh_list):
            generate_track_image(args, bh_data, bh_list, iibh)
    else:
        # Only plot the selected BH.
        iibh = np.nonzero(bh_list == args.bh_bid)[0]
        if len(iibh) == 1:
            generate_track_image(args, bh_data, bh_list, iibh[0])
        else:
            print(f"Could not (unambiguously) find BH-BID {args.bh_bid} "
                  f"in BH list.")

    print("Done!")
Beispiel #19
0
def transcribe_data(data_list,
                    vrfile_in,
                    outfile,
                    kind='simple',
                    form='scalar',
                    mixed_source=False):
    """Transcribe data sets.

    Parameters
    ----------
    data_list : tuple
        A list of the transcription keys to process. Each key is a tuple
        of (VR_name, Out_name, Comment, Conversion_Factor, Type_list).
    vrfile_in : str
        The VR file to transcribe data from.
    outfile : str
        The output file to store transcribed data in.
    kind : str
        The kind of transcription we are doing. Options are
            - 'main' --> main data transcription
            - 'profiles' --> transcribe profile data
            - 'apertures' --> transcribe aperture measurements
    form : str
        Form of data elements. Options are
            - 'scalar' --> simple scalar data quantity
            - '3darray' --> transcribe 3d array quantities
            - '3x3matrix' --> transcribe 3x3 matrix quantities
    mixed_source : bool, optional
        If True, index 6 specifies the source VR file and 'vrfile' is 
        assumed to be the common base instead.
    """
    for ikey in data_list:
        if len(ikey) < 5: set_trace()
        #if len(ikey) > 5 and kind != 'apertures': set_trace()

        # Deal with possibility of 'None' in Type_list (no type iteration)
        if ikey[4] is None:
            types = [None]
        else:
            types = ikey[4]

        # Deal with possibility of mixed-source input
        if mixed_source:
            if len(ikey) < 7:
                print("Need to specify source file in index 6 for "
                      "mixed source transcription!")
            vrfile = vrfile_in + ikey[6]
        else:
            vrfile = vrfile_in

        if not os.path.isfile(vrfile):
            print("Could not find input VR file...")
            set_trace()

        # Some quantities use capital X/Y/Z in VR...
        if ikey[0] in [
                'V?c', 'V?cmbp', 'V?cminpot', '?c', '?cmbp', '?cminpot'
        ]:
            dimsyms = dimsymbols_cap
        else:
            dimsyms = dimsymbols

        # Iterate over aperture types (only relevant for apertures)
        if kind == 'apertures':
            n_proj = 4
            ap_list = aperture_list
        else:
            n_proj = 1
            ap_list = [None]

        for iap in ap_list:
            for iproj in range(n_proj):

                # Some special treatment for apertures
                if kind == 'apertures':
                    if iproj > 0 and ikey[5] is False:
                        break

                    if iproj == 0:
                        ap_prefix = 'Aperture_'
                        ap_outfix = '/'
                    else:
                        ap_prefix = f'Projected_aperture_{iproj}_'
                        ap_outfix = f'/Projection{iproj}/'
                else:
                    ap_prefix = ''
                    ap_outfix = ''

                # Iterate over required types
                for itype in types:

                    # Construct type specifiers in in- and output
                    if itype is None:
                        typefix_in = ''
                        typefix_out = ''
                    else:
                        typefix_in = type_in[itype]
                        typefix_out = type_out[itype]

                    # Adjust comment
                    if itype is None:
                        comment = ikey[2].replace('#', '')
                    else:
                        comment = ikey[2].replace('#', type_self[itype])

                    # Construct the full data set names in in- and output
                    vrname = ikey[0] + typefix_in
                    outname = typefix_out + ikey[1]

                    # Deal with names for special cases
                    if kind == 'profiles':
                        outname = f'Profiles/{outname}'
                    elif kind == 'apertures':
                        vrname = f'{ap_prefix}{vrname}_{iap}_kpc'
                        outname = (f'ApertureMeasurements{ap_outfix}{iap}kpc/'
                                   f'{outname}')

                    # Transcribe data
                    if args.verbose:
                        print(f"{vrname} --> {outname}")

                    if form == '3darray':
                        outdata = np.zeros(
                            (num_haloes, 3), dtype=np.float32) - 1

                        # Load individual dimensions' data sets into output
                        for idim in range(3):
                            vrname_dim = vrname.replace('?', dimsyms[idim])
                            outdata[:, idim] = read_data(vrfile,
                                                         vrname_dim,
                                                         require=True)

                    elif form == '3x3matrix':
                        outdata = np.zeros(
                            (num_haloes, 3, 3), dtype=np.float32) - 1
                        for idim1 in range(3):
                            for idim2 in range(3):
                                vrname_dim = (vrname.replace(
                                    '?', dimsyms[idim1]).replace(
                                        '*', dimsyms[idim2]))
                                outdata[:, idim1,
                                        idim2] = read_data(vrfile,
                                                           vrname_dim,
                                                           require=True)
                    else:
                        # Standard case (scalar quantity)
                        outdata = read_data(vrfile, vrname, require=True)

                    if ikey[3] is not None:
                        outdata *= ikey[3]

                    write_data(outfile, outname, outdata, comment=comment)
Beispiel #20
0
def transcribe_metadata(vrfile_base, outfile, outfile_particles):
    """Transcribe the metadata"""

    # Set up the required VR file paths
    vrfile = vrfile_base + 'properties'
    vrfile_CPU = vrfile_base + 'catalog_particles.unbound'
    vrfile_CP = vrfile_base + 'catalog_particles'
    vrfile_prof = vrfile_base + 'profiles'
    vrfile_CS = vrfile_base + 'catalog_SOlist'

    # Copy the Header groups.
    # In future, this should be done properly, converting strings to numbers...
    f_in = h5.File(vrfile, 'r')
    f_out = h5.File(outfile, 'w')
    f_in.copy('Configuration', f_out)
    f_in.copy('SimulationInfo', f_out)
    f_in.copy('UnitInfo', f_out)
    f_in.close()
    f_out.close()

    # Read metadata stored in datasets
    num_haloes_total = read_data(vrfile, 'Total_num_of_groups')[0]
    num_haloes = read_data(vrfile,
                           'Num_of_groups')[0]  # Note confusing nomenclature
    num_groups = read_data(vrfile_prof, 'Num_of_halos')[0]  # ....
    num_groups_total = read_data(vrfile_prof, 'Total_num_of_halos')[0]  # ....
    file_id = read_data(vrfile, 'File_id')[0]
    file_num = read_data(vrfile, 'Num_of_files')[0]
    num_bound = read_data(vrfile_CP, 'Num_of_particles_in_groups')[0]
    num_bound_total = read_data(vrfile_CP,
                                'Total_num_of_particles_in_all_groups')[0]
    num_unbound = read_data(vrfile_CPU, 'Num_of_particles_in_groups')[0]
    num_unbound_total = read_data(vrfile_CPU,
                                  'Total_num_of_particles_in_all_groups')[0]
    num_part_so = read_data(vrfile_CS, 'Num_of_particles_in_SO_regions')[0]
    num_part_so_total = read_data(vrfile_CS,
                                  'Total_num_of_particles_in_SO_regions')[0]
    flag_inclusive_profiles = read_data(vrfile_prof,
                                        'Inclusive_profiles_flag')[0]
    num_bin_edges = read_data(vrfile_prof, 'Num_of_bin_edges')[0]
    radial_bin_edges = read_data(vrfile_prof, 'Radial_bin_edges')
    type_radial_bins = read_data(vrfile_prof, 'Radial_norm')[0]

    # Write general metadata to 'Header'
    write_attribute(outfile, 'Header', 'NumberOfHaloes', num_haloes)
    write_attribute(outfile, 'Header', 'NumberOfHaloes_Total',
                    num_haloes_total)
    write_attribute(outfile, 'Header', 'NumberOfGroups', num_groups)
    write_attribute(outfile, 'Header', 'NumberOfGroups_Total',
                    num_groups_total)
    write_attribute(outfile, 'Header', 'NumberOfFiles', file_num)
    write_attribute(outfile, 'Header', 'FileID', file_id)
    write_attribute(outfile, 'Header', 'NumberOfBoundParticles', num_bound)
    write_attribute(outfile, 'Header', 'NumberOfBoundParticles_Total',
                    num_bound_total)
    write_attribute(outfile, 'Header', 'NumberOfUnboundParticles', num_unbound)
    write_attribute(outfile, 'Header', 'NumberOfUnboundParticles_Total',
                    num_unbound_total)
    write_attribute(outfile, 'Header', 'NumberOfSOParticles', num_part_so)
    write_attribute(outfile, 'Header', 'NumberOfSOParticles_Total',
                    num_part_so_total)

    # Write profile-specific metadata directly to that group
    write_attribute(outfile, 'Profiles', 'Flag_Inclusive',
                    flag_inclusive_profiles)
    write_attribute(outfile, 'Profiles', 'NumberOfBinEdges', num_bin_edges)
    write_attribute(outfile, 'Profiles', 'TypeOfBinEdges', type_radial_bins)
    write_attribute(outfile, 'Profiles', 'RadialBinEdges', radial_bin_edges)

    # Copy Header to particles file
    f_out = h5.File(outfile, 'r')
    f_part = h5.File(outfile_particles, 'w')
    f_out.copy('Header', f_part)
    f_out.close()
    f_part.close()

    return num_haloes, num_groups
Beispiel #21
0
def connect_to_galaxies(bpart_ids, vr_file, combined_vr=True,
                        extra_props=None):
    """Connect black holes to galaxies at a specified redshift.

    Parameters
    ----------
    bpart_ids : ndarray(int)
        The IDs of the black hole particle to match.
    vr_file : string
        VR file to connect to (None for no matching)
    combined_vr : bool, optional
        Flag for using transcribed VR, must currently be True.
    extra_props : list of tuples, optional
        Extra properties to be retrieved from the VR catalogue. If None
        (default), none are extracted. Entries must be of the form
        ('VR_dataset', 'Output_name').

    Returns
    -------
    gal_props : dict
        Dict with the results of the VR matching:
        - Haloes --> VR halo indices for each BH, -1 if unmatched
        - MStar --> Stellar mass of the host halo [M_Sun] within 30 pkpc,
                    np.nan if not matched
        - SFR --> SFR of the host halo [M_Sun/yr] within 30 pkpc, np.nan
                  if not matched
        - M200c --> M200c of the host halo [M_Sun], np.nan if not matched
        - HaloTypes --> Type index of the host halo, -1 if not matched
        - [other properties as given in extra_props dict]
        - Redshift --> Redshift of VR catalogue
    """

    num_bhs = len(bpart_ids)
    gal_props = {}

    if vr_file is None:
        print("Skipping galaxy linking on your request...")
        return

    # Look up BH IDs in VR
    gal_props['Haloes'], gal_props['ScaleFactor'], gal_props['Redshift'] = (
        connect_ids_to_vr(bpart_ids, vr_file, require=False))
    
    # Add a few key properties of the haloes, for convenience
    ind_in_halo = np.nonzero(gal_props['Haloes'] >= 0)[0]
    vr_halo = gal_props['Haloes']
    
    vr_main_file = f'{vr_file}.hdf5'
    vr_mstar = hd.read_data(vr_main_file,
                            'ApertureMeasurements/30kpc/Stars/Masses')
    vr_sfr = hd.read_data(vr_main_file, 'ApertureMeasurements/30kpc/SFR/')
    vr_m200c = hd.read_data(vr_main_file, 'M200crit')
    vr_haloTypes = hd.read_data(vr_main_file, 'StructureTypes')
    
    gal_props['MStar'] = np.zeros(num_bhs) + np.nan
    gal_props['SFR'] = np.zeros(num_bhs) + np.nan
    gal_props['M200c'] = np.zeros(num_bhs) + np.nan
    gal_props['HaloTypes'] = np.zeros(num_bhs, dtype=int) - 1
    
    gal_props['MStar'][ind_in_halo] = vr_mstar[vr_halo[ind_in_halo]]
    gal_props['SFR'][ind_in_halo] = vr_sfr[vr_halo[ind_in_halo]]
    gal_props['M200c'][ind_in_halo] = vr_m200c[vr_halo[ind_in_halo]]
    gal_props['HaloTypes'][ind_in_halo] = vr_haloTypes[vr_halo[ind_in_halo]]

    if extra_props is not None:
        for iextra in extra_props:
            print(f"Extracting extra quantity '{iextra[0]}' --> "
                  f"'{iextra[1]}'")
            vr_quant = hd.read_data(vr_main_file, iextra[0])
            dtype = vr_quant.dtype
            nan_value = np.nan
            if issubclass(dtype.type, np.integer):
                nan_value = -999999
            gal_props[iextra[1]] = np.zeros(num_bhs, dtype) + nan_value
            gal_props[iextra[1]][ind_in_halo] = vr_quant[vr_halo[ind_in_halo]]

    return gal_props
Beispiel #22
0
def lookup_bh_data(bh_data_file, bh_props_list, selection_list=None,
                   bh_list=None):
    """Load info from BH file into arrays and find target BHs."""

    bh_data = {}

    # Make sure all selectors are part of the bh_props_list
    if selection_list is not None:
        for isel in selection_list:
            if isel[0] not in bh_props_list:
                print(f"Selector '{isel[0]}' was not in requested BH props "
                      f"list, adding it now...")
                bh_props_list.append(isel[0])
    
    # Load the required data from the evolution tables
    num_bhs = None
    for idata in bh_props_list:
        data = hd.read_data(bh_data_file, idata)

        # May not get all data, such as VR links 
        if data is not None:
            bh_data[idata] = data
            num_bhs = data.shape[0]

            
    # Now find the list of BHs we are interested in (based on z=0 props)

    if bh_list is not None:
        sel = bh_list
    elif selection_list is None:
        sel = None

    else:
        if num_bhs is None:
            # Did not load any properties, so cannot select anything. Doh.
            print("Cannot select any BHs, because we did not load any data.")
            set_trace()

        sel = np.arange(num_bhs, dtype=int)
        for selector in selection_list:
            
            # Apply requested comparison operation
            cmp = ops[selector[1]]
            try:
                selector_data = bh_data[selector[0]][sel]
                if len(selector_data.shape) == 1:
                    ind_subsel = np.nonzero((cmp(bh_data[selector[0]][sel],
                                                 selector[2])) &
                                            (bh_data[selector[0]][sel] * 0 == 0))[0]
                elif len(selector_data.shape) == 2:
                    ind_subsel = np.nonzero((cmp(selector_data[:, selector[3]],
                                                 selector[2])) &
                                            (selector_data[:, selector[3]] * 0 == 0))[0]
                else:
                    print("3+ dimensional selector arrays not yet handled.")
                    set_trace()
                    
            except KeyError:
                print(f"Could not locate selector '{selector[0]}' in BH data "
                      "file.")
                if 'Halo' in selector[0]:
                    print("Assuming that halo linking was not done, skipping "
                          "this comparison.")
                    continue
                else:
                    set_trace()
                
            # Adjust selection list
            sel = sel[ind_subsel]

        # Sort BHs by index
        sel = np.sort(sel)
        print(f"There are {len(sel)} BHs in selection list.")

    return bh_data, sel
Beispiel #23
0
dirs1 = glob.glob(f'{local.BASE_DIR}/ID{args.sim1}*/')
dirs2 = glob.glob(f'{local.BASE_DIR}/ID{args.sim2}*/')
if len(dirs1) != 1 or len(dirs2) != 1:
    set_trace()
wdir1 = dirs1[0]
wdir2 = dirs2[0]
print(f"Analysing simulation {wdir1}")
print(f"Matching into simulation {wdir2}")

# Look up info about target BH in REF simulation and snapshot
datafile_ref = wdir1 + f'/{args.name1}_{args.snapshot:04d}.hdf5'

if args.index is not None:
    bh_id = hd.read_data(datafile_ref,
                         'PartType5/ParticleIDs',
                         read_index=args.index)
else:
    if args.id is None:
        print("Well, we need either an index or an ID!")
        set_trace()
    bh_ids = hd.read_data(datafile_ref, 'PartType5/ParticleIDs')
    args.index = np.nonzero(bh_ids == args.id)[0]
    if len(args.index) != 1:
        print(f"Could not unambiguously find BH ID {args.id}...")
        set_trace()
    args.index = args.index[0]
    bh_id = args.id

bh_ft = hd.read_data(datafile_ref,
                     'PartType5/FormationScaleFactors',
Beispiel #24
0
def connect_to_galaxies(bpart_ids, args):
    """Connect black holes to galaxies at z = 0."""

    if args.vr_snap is None:
        print("Skipping galaxy linking on your request...")
        return

    if args.combined_vr:
        args.vr_particles = args.wdir + f'{args.vr_file}_{args.vr_snap:04d}_particles.hdf5'
        args.vr_outfile = args.wdir + f'{args.vr_file}_{args.vr_snap:04d}.hdf5'
    else:
        print("Please transcribe VR catalogue...")
        set_trace()

    aexp = float(
        hd.read_attribute(args.vr_outfile, 'SimulationInfo', 'ScaleFactor'))
    args.vr_zred = 1 / aexp - 1
    args.vr_aexp = aexp

    print(f"Connecting to VR snapshot {args.vr_snap} at redshift "
          f"{args.vr_zred}...")

    # Load VR particle IDs
    vr_ids = hd.read_data(args.vr_particles, 'Haloes/IDs')
    vr_nums = hd.read_data(args.vr_particles, 'Haloes/Numbers')
    vr_offsets = hd.read_data(args.vr_particles, 'Haloes/Offsets')

    # Locate 'our' BHs in the VR ID list
    print("Locating BHs in VR list...")
    stime = time.time()
    ind_in_vr, found_in_vr = hx.find_id_indices(bpart_ids, vr_ids)
    print(f"... took {(time.time() - stime):.3f} sec., located "
          f"{len(found_in_vr)} "
          f"/ {len(bpart_ids)} BHs in VR list "
          f"({len(found_in_vr)/len(bpart_ids)*100:.3f}%).")

    # Now convert VR index to halo
    bh_halo = np.zeros(len(bpart_ids), dtype=int) - 1
    halo_guess = np.searchsorted(
        vr_offsets, ind_in_vr[found_in_vr], side='right') - 1
    ind_good = np.nonzero(ind_in_vr[found_in_vr] < (vr_offsets[halo_guess] +
                                                    vr_nums[halo_guess]))[0]
    bh_halo[found_in_vr[ind_good]] = halo_guess[ind_good]

    print(f"... could match {len(ind_good)} / {len(bpart_ids)} BHs to haloes. "
          f"({len(ind_good)/len(bpart_ids)*100:.3f}%).")

    gal_props = {'halo': bh_halo}

    # Add a few key properties of the haloes, for convenience
    ind_in_halo = found_in_vr[ind_good]

    vr_mstar = hd.read_data(args.vr_outfile,
                            'ApertureMeasurements/30kpc/Stars/Masses')
    vr_sfr = hd.read_data(args.vr_outfile, 'ApertureMeasurements/30kpc/SFR/')
    vr_m200c = hd.read_data(args.vr_outfile, 'M200crit')
    vr_haloTypes = hd.read_data(args.vr_outfile, 'StructureTypes')

    gal_props['MStar'] = np.zeros(len(bpart_ids))
    gal_props['SFR'] = np.zeros(len(bpart_ids))
    gal_props['M200'] = np.zeros(len(bpart_ids))
    gal_props['HaloTypes'] = np.zeros(len(bpart_ids), dtype=int)

    gal_props['MStar'][ind_in_halo] = vr_mstar[bh_halo[ind_in_halo]]
    gal_props['SFR'][ind_in_halo] = vr_sfr[bh_halo[ind_in_halo]]
    gal_props['M200'][ind_in_halo] = vr_m200c[bh_halo[ind_in_halo]]
    gal_props['HaloTypes'][ind_in_halo] = vr_haloTypes[bh_halo[ind_in_halo]]

    return gal_props
Beispiel #25
0
def process_sim(isim, args):
    """Process one individual simulation."""

    if args.have_full_sim_dir:
        args.wdir = isim
    else:
        args.wdir = xl.get_sim_dir(args.base_dir, isim)
    print(f"Analysing simulation {args.wdir}...")

    # Name of the input catalogue, containing all the data to plot
    args.catloc = f'{args.wdir}{args.bh_file}'
    bh_props_list = ['ParticleIDs']
    
    # Select BHs in this sim
    args.plotdata_file = f'{args.wdir}{args.vrplot_prefix}.hdf5'
    if os.path.isfile(args.plotdata_file):
        bh_list = hd.read_data(args.plotdata_file, 'BlackHoleBIDs')
        select_list = None

    elif args.bh_bid is None:

        # Find BHs we are intereste in, load data
        select_list = [
            ["Halo_MStar", '>=', args.halo_mstar_range[0]],
            ["Halo_MStar", '<', args.halo_mstar_range[1]],
            ["Halo_M200c", '>=', args.halo_m200_range[0]],
            ["Halo_M200c", '<', args.halo_m200_range[1]]
        ]
        if not args.include_subdominant_bhs:
            select_list.append(['Flag_MostMassiveInHalo', '==', 1])
        if not args.include_satellites:
            select_list.append(['HaloTypes', '==', 10])

        if args.bh_mass_range is not None:
            zreds = hd.read_data(args.wdir + args.bh_file, 'Redshifts')
            best_index = np.argmin(np.abs(zreds - args.bh_selection_redshift))
            print(f"Best index for redshift {args.bh_selection_redshift} is "
                  f"{best_index}.")
            
            # Subgrid masses are in 10^10 M_sun, so need to adjust selection
            # range
            select_list.append(
                ['SubgridMasses', '>=', args.bh_mass_range[0]/1e10, best_index])
            select_list.append(
                ['SubgridMasses', '<=', args.bh_mass_range[1]/1e10, best_index])
        bh_list = None
            
    else:
        bh_list = args.bh_bid
        select_list = None

    bh_file = args.wdir + args.bh_file
    bh_data, bh_sel = xl.lookup_bh_data(bh_file, bh_props_list, select_list)

    if bh_list is None:
        bh_list = bh_sel

    if len(bh_list) == 0:
        print("No black holes selected, aborting.")
        return

    
    # Overwrite selection with input, if specific BID(s) provided
    if args.bh_bid is not None:
        bh_list = args.bh_bid    

    for isnap in args.snapshots:
        process_snapshot(args, bh_list, bh_data, isim, isnap)
Beispiel #26
0
if args.imtype in ['temp', 'diffusion_parameters', 'sfr'] and args.ptype != 0:
    print(f"{args.imtype} is only available for gas.")
    set_trace()

if args.nosave:
    save_maps = False

if zsize is None:
    args.zsize = args.imsize
else:
    args.zsize = zsize

if args.cambhbid is not None:
    black_file = args.rootdir + args.bh_data_file
    args.cambhid = hd.read_data(black_file,
                                'ParticleIDs',
                                read_index=args.cambhbid)

if not save_maps and args.noplot:
    print("If we don't want any output, we can stop right here.")
    set_trace()

args.realimsize = args.imsize
args.realzsize = args.zsize


def image_snap(isnap):
    """Main function to image one specified snapshot."""

    print(f"Beginning imaging snapshot {isnap}...")
    stime = time.time()
Beispiel #27
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 #28
0
def find_black_ids(args):
    """Get the IDs of all black holes that ever existed in a simulation."""

    # List holding all black hole particle IDs, starts with zero elements.
    particle_ids_set = set()

    # We only use the above set for efficient finding of new members. Actual
    # "membership list" is kept in a separate array, so we can keep it aligned
    # with the list of first snapshots.
    particle_ids = np.zeros(0, dtype=int)
    first_snaps = np.zeros(0, dtype=int)

    for isnap in range(args.max_snap + 1):
        snapfile = args.wdir + args.snap_name + f'_{isnap:04d}.hdf5'
        if not os.path.isfile(snapfile):
            continue

        # Load IDs of all black holes existing in current output
        bpart_ids = hd.read_data(snapfile, 'PartType5/ParticleIDs')
        if bpart_ids is None:
            print(f"Did not find any black holes in output {isnap}...")
            continue
        else:
            #print(f"Processing output {isnap}...")
            bpart_ids = bpart_ids.astype(int)
            # Update first/last-snap-with-BHs tracker
            args.last_snap = isnap
            if args.first_snap is None:
                args.first_snap = isnap

        # Check which of these are new to the club
        if len(particle_ids_set) == 0:
            ind_new = np.arange(len(bpart_ids))
        else:
            status = [_id in particle_ids_set for _id in bpart_ids]
            ind_new = [i for i, val in enumerate(status) if not val]
            #ind_new = np.nonzero(status is False)[0]
            #    bhids, ind_old = hx.find_id_indices(bpart_ids, particle_ids)
            #ind_new = np.nonzero(bhids < 0)[0]

        print(f"Found {len(ind_new)} new BHs in output {isnap} (out of "
              f"{len(bpart_ids)}).")

        # Subscribe all new members
        if len(ind_new) > 0:
            for inew in ind_new:
                particle_ids_set.add(bpart_ids[inew])

            particle_ids = np.concatenate((particle_ids, bpart_ids[ind_new]))
            first_snaps = np.concatenate(
                (first_snaps, np.zeros(len(ind_new), dtype=int) + isnap))

    # Done looping through outputs, report what we caught
    particle_ids_from_set = np.array(list(particle_ids_set))

    if len(particle_ids_from_set) != len(particle_ids):
        print("Inconsistent number of caught BHs!")
        set_trace()
    if np.max(np.abs(np.sort(particle_ids_from_set) -
                     np.sort(particle_ids))) > 0:
        print("Inconsistent IDs of caught BHs!")
        set_trace()

    args.num_bhs = len(particle_ids)
    if args.first_snap is not None:
        args.num_bh_snaps = args.last_snap - args.first_snap + 1
        first_snaps -= args.first_snap
    else:
        args.num_bh_snaps = 0
    print(f"Found a total of {args.num_bhs} black holes in "
          f"{args.num_bh_snaps} snapshots.")

    return particle_ids, first_snaps
Beispiel #29
0
def process_sim(isim, args):
    """Process one specific simulation."""

    if args.have_full_sim_dir:
        args.wdir = isim
    else:
        args.wdir = xl.get_sim_dir(args.base_dir, isim)
    print(f"Analysing simulation {args.wdir}...")

    # Name of the input catalogue, containing all the data to plot
    args.catloc = f'{args.wdir}{args.bh_file}'

    # Select BHs in this sim
    args.plotdata_file = f'{args.wdir}{args.vrplot_prefix}.hdf5'
    if os.path.isfile(args.plotdata_file):
        bh_list = hd.read_data(args.plotdata_file, 'BlackHoleBIDs')
        select_list = None

    elif args.bh_bid is not None:
        select_list = None
        bh_list = args.bh_bid

    else:

        # Find BHs we are intereste in, load data
        select_list = [["Halo_MStar", '>=', args.halo_mstar_range[0]],
                       ["Halo_MStar", '<', args.halo_mstar_range[1]],
                       ["Halo_M200c", '>=', args.halo_m200_range[0]],
                       ["Halo_M200c", '<', args.halo_m200_range[1]]]
        if not args.include_subdominant_bhs:
            select_list.append(['Flag_MostMassiveInHalo', '==', 1])
        if not args.include_satellites:
            select_list.append(['HaloTypes', '==', 10])

        if args.bh_mass_range is not None:
            zreds = hd.read_data(args.wdir + args.bh_file, 'Redshifts')
            best_index = np.argmin(np.abs(zreds - args.bh_selection_redshift))
            print(f"Best index for redshift {args.bh_selection_redshift} is "
                  f"{best_index}.")

            # Subgrid masses are in 10^10 M_sun, so need to adjust selection
            # range
            select_list.append([
                'SubgridMasses', '>=', args.bh_mass_range[0] / 1e10, best_index
            ])
            select_list.append([
                'SubgridMasses', '<=', args.bh_mass_range[1] / 1e10, best_index
            ])

        bh_list = None

    bh_file = args.wdir + args.bh_file
    bh_data, bh_list = xl.lookup_bh_data(bh_file, bh_props_list, select_list,
                                         bh_list)
    args.nsnap = len(bh_data['Times'])

    # Extract meta-data from Header
    bh_data['CodeBranch'] = hd.read_attribute(bh_file, 'Code', 'Git Branch')
    bh_data['CodeDate'] = hd.read_attribute(bh_file, 'Code', 'Git Date')
    bh_data['CodeRev'] = hd.read_attribute(bh_file, 'Code', 'Git Revision')
    bh_data['SimName'] = hd.read_attribute(bh_file, 'Header', 'RunName')

    # Look up stars data
    stars = Stars(args)

    for ibh in bh_list:
        process_bh(args, stars, bh_data, ibh, isim)