Book-keeping of halos, subhalos and galaxies
This section outlines the book-keeping associated with:
the ordering of galaxies
the inheritance of properties from progenitors
I have omitted lines from the code blocks that are not relevant to this.
Looping over graphs and snapshots
# Loop over graphs
for i_graph in range(min(parameters.n_graph,n_GRAPH)):
graph = C_graph(i_graph,graph_file,parameters)
# Loop over snapshots
halos_last_snap = None
subs_last_snap = None
gals_last_snap = None
for i_snap in graph.snap_ID:
# Initialise halo and subhalo properties.
halos_this_snap = [C_halo(i_graph,i_snap,i_halo,graph,parameters) for i_halo in
graph.halo_start_gid[i_snap]+range(graph.n_halo_snap[i_snap])]
subs_this_snap = None
if graph.n_sub > 0:
if graph.n_sub_snap[i_snap] > 0:
subs_this_snap = [C_sub(i_graph,i_snap,i_sub,graph,parameters)
for i_sub in graph.sub_start_gid[i_snap]+range(graph.n_sub_snap[i_snap])]
# Propagate information from progenitors to this generation and initialise galaxies
gals_this_snap=F_update_halos(halos_last_snap,halos_this_snap,subs_last_snap,
subs_this_snap,gals_last_snap,graph,parameters)
del halos_last_snap
del subs_last_snap
del gals_last_snap
# Process the halos
F_process_halos(halos_this_snap,subs_this_snap,gals_this_snap,graph,parameters)
# Once all halos have been done, output results
# This could instead be done on a halo-by-halo basis in F_process_halos
halo_output.append(halos_this_snap,parameters)
if subs_this_snap != None: sub_output.append(subs_this_snap,parameters)
if gals_this_snap != None: gal_output.append(gals_this_snap,parameters)
# Rename this_snap data structures to last_snap
halos_last_snap=halos_this_snap
subs_last_snap=subs_this_snap
gals_last_snap=gals_this_snap
# Delete old references (so that create new objects on next snapshot)
del halos_this_snap
del subs_this_snap
del gals_this_snap
# Tidy up
del halos_last_snap
del subs_last_snap
del gals_last_snap
Initialisation of halos and subhalos
Within each snapshot, halos, subhalo and galaxies are stored as python lists. When passing forward information from one snapshot to the next, two copies of these lists are required: one for the progenitor and one for the new snapshot; for subsequent processing, only the current snapshot is required.
halos_this_snap = [C_halo(i_graph,i_snap,i_halo,graph,parameters) for i_halo in
graph.halo_start_gid[i_snap]+range(graph.n_halo_snap[i_snap])]
subs_this_snap = None
if graph.n_sub > 0:
if graph.n_sub_snap[i_snap] > 0:
subs_this_snap = [C_sub(i_graph,i_snap,i_sub,graph,parameters)
for i_sub in graph.sub_start_gid[i_snap]+range(graph.n_sub_snap[i_snap])]
During the initialisation, pointers the host halo and related subhalos, that are available from the graph, are read in.
Propagation of information from progenitors
Note that this also generates the galaxy array:
# Have to do this even if no progenitors in order to initialise galaxy array
gals_this_snap=F_update_halos(halos_last_snap,halos_this_snap,subs_last_snap,
subs_this_snap,gals_last_snap,graph,parameters)
Galaxies are stored in halo order. Within each halo, we first have orphan galaxies (i.e. those who have lost their subhalos) followed by the galaxies associated with each subhalo, in subhalo order).
We need to do a first pass to push halo/subhalo properties and to determine the number of galaxies. This also sets pointers in the halo and subhalo instances of where the associated galaxy and orphan galaxy counts start.
The first code block loops over halos, giving mass and hot gas to it’s descendants in proportion to their overlap, and all galaxies to the main descendant (the one with the most overlap). I have omitted some of the lines, for clarity; this just shows the overall structure of the block.
# Loop over halos
if halos_last_snap != None:
for halo in halos_last_snap:
# First determine what fraction to give to each descendant
desc_start_gid=halo.desc_start_gid
desc_end_gid=halo.desc_end_gid
if (halo.n_desc==0):
# For now just skip this halo; might want in future to log these occurrences
continue
fractions=graph.desc_contribution[desc_start_gid:desc_end_gid]/ \
np.sum(graph.desc_contribution[desc_start_gid:desc_end_gid])
# The main descendant is the one that inherits the greatest contribution
desc_main_sid=graph.desc_IDs_gid[desc_start_gid+np.argmax(fractions)]-halo_offset
halo.desc_main_sid=desc_main_sid
# All orphans gals go to main descendant so increase relevant orphan count
halos_this_snap[desc_main_sid].n_orphan+=halo.n_orphan
# Now loop over descendants transferring properties to them:
for i_desc in range(desc_start_gid,desc_end_gid):
desc_halo=halos_this_snap[graph.desc_IDs_gid[i_desc]-halo_offset]
# Distribute mass to descendants in proportion to fractional contributions
i_frac=i_desc-desc_start_gid # fraction index corresponding to descendent index i_desc
desc_halo.mass_from_progenitors+=fractions[i_frac]*halo.mass
# ... repeat for other baryonic properties ...
Next we loop over subhalos. For now the main descendent subhalo gets everything. If there is no descendent then the hot gas and galaxies get given to the descendent of the host halo.
# Now loop over the subhalos
if subs_last_snap != None:
for sub in subs_last_snap:
sub_desc_start_gid=sub.desc_start_gid
sub_desc_end_gid=sub.desc_end_gid
host_sid=sub.host-halo_offset_last
desc_main_sid=halos_last_snap[host_sid].desc_main_sid
if sub.n_desc==0:
# If no descendant, subhalo components get given to the (main descendant of) the host halo
# and gals become orphans of that halo. So add to relevant orphan count.
halos_this_snap[desc_main_sid].n_orphan+=sub.n_gal
# ... add subhalo baryons to the descendent halo ...
else:
# Otherwise the main subhalo descendant gets all the gals and hot gas -
# i.e. assume that subhalos cannot split.
fractions=graph.sub_desc_contribution[sub_desc_start_gid:sub_desc_end_gid]/ \
np.sum(graph.sub_desc_contribution[sub_desc_start_gid:sub_desc_end_gid])
sub_desc_main_sid=graph.sub_desc_IDs_gid[sub_desc_start_gid+np.argmax(fractions)]-sub_offset
sub.desc_main_sid=sub_desc_main_sid
subs_this_snap[sub_desc_main_sid].n_gal+=sub.n_gal
# ... add subhalo baryons to the main descendent subhalo ...
Next we count the total number of galaxies and initialise the galaxy numpy array:
# Now count the total number of gals and generate the gal array.
# This is done as a loop over subhalos within halos so as to keep all gals in a halo
# closely associated in the array.
n_gal=0
for halo in halos_this_snap:
n_gal_start=n_gal
if halo.n_sub>0:
for sub in subs_this_snap[halo.sub_start_sid:halo.sub_end_sid]:
# Record the location of this subhalo's gals in the gal lookup table. This also updates n_gal.
n_gal=sub.gal_loc(n_gal)
# Record the starting location of all this halos gals, and of of its orphans, in the gal lookup table,
# and update n_gal to include the orphans.
n_gal=halo.gal_loc(n_gal_start,n_gal)
if n_gal==0: return None
# Create new gal array and initially set all entries to empty and existence to True
gals_this_snap=np.empty(n_gal,dtype=D_gal)
gals_this_snap[:]=gal_template
# Set galaxy gids and update graph galaxy counter (in that order).
gals_this_snap['gal_gid']=graph.n_gal+np.arange(n_gal)
graph.n_gal+=n_gal
Now we do a second pass to populate galaxies with inherited properties:
# Second loop to pass on gal properties.
if gals_last_snap != None:
if parameters.b_debug:
print('Pushing gals',flush=True)
for halo in halos_last_snap:
if halo.b_desc_exists == False: continue
n_orphan=halo.n_orphan
if n_orphan > 0:
# match up orphans
desc_halo=halos_this_snap[halo.desc_main_sid]
# The is the location of orphan galaxies in the previous snapshot
gal_last_start_sid=halo.orphan_start_sid
gal_last_end_sid=gal_last_start_sid+n_orphan
# and in the current snapshot
gal_this_start_sid=desc_halo.orphan_count(n_orphan)
gal_this_end_sid=gal_this_start_sid+n_orphan
# Copy over all properties
gals_this_snap[gal_this_start_sid:gal_this_end_sid]=gals_last_snap[gal_last_start_sid:gal_last_end_sid]
# Update the tracking pointers
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['halo_gid']=desc_halo.halo_gid
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['halo_sid']=desc_halo.halo_gid-halo_offset
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['sub_gid']=parameters.NO_DATA_INT
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['sub_sid']=parameters.NO_DATA_INT
# Inherited orphans will not have merged (I think); otherwise the following line could be overwritten
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['first_prog_gid']=np.arange(gal_this_start_sid,gal_this_end_sid)
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['next_prog_gid']=parameters.NO_DATA_INT
if subs_last_snap != None:
for sub in subs_last_snap:
n_sub_gal=sub.n_gal
sub_desc_start_gid=sub.desc_start_gid
sub_desc_end_gid=sub_desc_start_gid+sub.n_desc
gal_last_start_sid=sub.gal_start_sid
gal_last_end_sid=gal_last_start_sid+n_sub_gal
if sub.n_desc==0:
# If no descendant gals become orphans of (the main descendant of) the host halo
desc_halo=halos_this_snap[sub.desc_host_sid]
gal_this_start_sid=desc_halo.orphan_count(n_sub_gal)
gal_this_end_sid=gal_this_start_sid+n_sub_gal
# Copy over all properties
gals_this_snap[gal_this_start_sid:gal_this_end_sid]=gals_last_snap[gal_last_start_sid:gal_last_end_sid]
# Update the tracking pointers
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['halo_gid']=desc_halo.halo_gid
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['halo_sid']=desc_halo.halo_gid-halo_offset
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['sub_gid']=parameters.NO_DATA_INT
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['sub_sid']=parameters.NO_DATA_INT
# New orphans will not have merged (I think); otherwise the following line could be overwritten
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['first_prog_gid']=np.arange(gal_this_start_sid,gal_this_end_sid)
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['next_prog_gid']=parameters.NO_DATA_INT
else:
# Otherwise the main subhalo descendant gets all the gals
desc_sub=subs_this_snap[sub.desc_main_sid]
desc_halo=halos_this_snap[sub.desc_host_sid]
# Obtain current galaxy counter for this subhalo
gal_this_start_sid=desc_sub.gal_count(n_sub_gal)
gal_this_end_sid=gal_this_start_sid+n_sub_gal
# Copy over all properties
gals_this_snap[gal_this_start_sid:gal_this_end_sid]=gals_last_snap[gal_last_start_sid:gal_last_end_sid]
# Update the tracking pointers
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['halo_gid']=desc_halo.halo_gid
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['halo_sid']=desc_halo.halo_gid-halo_offset
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['sub_gid']=desc_sub.sub_gid
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['sub_sid']=desc_sub.sub_gid-sub_offset
# This is probably wrong: we need to check if there is already an entry for
# first_prog_sid for these galaxies and, if so, update next_prog_sid to point to it.
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['first_prog_gid']=np.arange(gal_this_start_sid,gal_this_end_sid)
gals_this_snap[gal_this_start_sid:gal_this_end_sid]['next_prog_gid']=parameters.NO_DATA_INT
gals_this_snap['graph_ID']=graph.graph_ID
gals_this_snap['snap_ID']=halos_this_snap[0].snap_ID