def make_map(self, particle_type: int, weights: unyt.array, tilt: str = 'z') -> np.ndarray: cop = self.data.subfind_tab.FOF.GroupCentreOfPotential R500c = self.data.subfind_tab.FOF.Group_R_Crit500 coord = self.data.subfind_particles[f'PartType{particle_type}'][ 'Coordinates'] coord_rot = self.rotate_coordinates(particle_type, tilt=tilt) smoothing_lengths = self.data.subfind_particles[ f'PartType{particle_type}']['SmoothingLength'] aperture = unyt.unyt_quantity(5 * R500c / np.sqrt(3), coord.units) if particle_type == 0: temperature = self.data.subfind_particles['PartType0'][ 'Temperature'] spatial_filter = np.where( (np.abs(coord_rot[:, 0]) < aperture) & (np.abs(coord_rot[:, 1]) < aperture) & (np.abs(coord_rot[:, 2]) < aperture) & (temperature.value > self.hot_gas_temperature_threshold))[0] else: spatial_filter = np.where((np.abs(coord_rot[:, 0]) < aperture) & (np.abs(coord_rot[:, 1]) < aperture) & (np.abs(coord_rot[:, 2]) < aperture))[0] read.pprint(coord_rot, cop, spatial_filter, sep='\n') x_max = np.max(coord_rot[spatial_filter, 0]) x_min = np.min(coord_rot[spatial_filter, 0]) y_max = np.max(coord_rot[spatial_filter, 1]) y_min = np.min(coord_rot[spatial_filter, 1]) x_range = x_max - x_min y_range = y_max - y_min x = (coord_rot[spatial_filter, 0] - x_min) / x_range y = (coord_rot[spatial_filter, 1] - y_min) / y_range h = smoothing_lengths[spatial_filter] / (2 * aperture) # Gather and handle coordinates to be processed x = np.asarray(x.value, dtype=np.float64) y = np.asarray(y.value, dtype=np.float64) m = np.asarray(weights[spatial_filter].value, dtype=np.float32) h = np.asarray(h.value, dtype=np.float32) smoothed_map = scatter(x=x, y=y, m=m, h=h, res=self.resolution).T smoothed_map = np.ma.masked_where( np.abs(smoothed_map) < 1.e-9, smoothed_map) surface_element = x_range * y_range / self.resolution**2 setattr(Mapping, f'surface_element{particle_type}', surface_element) return smoothed_map # * weights.units / coord.units ** 2
def generate_volume(*args, parallel = False): """ SWIFTSIMIO WRAPPER ------------------ The key here is that only particles in the domain [0, 1] in x, [0, 1] in y and [0, 1] in z will be visible in the image. You may have particles outside of this range; they will not crash the code, and may even contribute to the image if their smoothing lengths overlap with [0, 1]. You will need to re-scale your data such that it lives within this range. out will be a 3D numpy grid of shape [res, res, res]. You will need to re-scale this back to your original dimensions to get it in the correct units, and do not forget that it now represents the smoothed quantity per surface volume. ================================ @jit(nopython=True, fastmath=True) def scatter( x: float64, y: float64, z: float64, m: float32, h: float32, res: int ) -> ndarray: Creates a voxel grid of: + x: the x-positions of the particles. Must be bounded by [0, 1]. + y: the y-positions of the particles. Must be bounded by [0, 1]. + z: the z-positions of the particles. Must be bounded by [0, 1]. + m: the masses (or otherwise weights) of the particles + h: the smoothing lengths of the particles + res: the number of voxels along one axis, i.e. this returns a cube of res * res * res.. ================================ This ignores boundary effects. Note that explicitly defining the types in this function allows for a 25-50% performance improvement. In our testing, using numpy floats and integers is also an improvement over using the numba ones. :param parallel: (boolean) default = False Triggers the use of Numba decorators for parallel rendering. :param kwargs: parse the kwargs used by swiftsimio.visualisation.volume_render.scatter_parallel :return: nd array """ from swiftsimio.visualisation.volume_render import scatter, scatter_parallel print('[ SWIFTSIMIO ]\t ==> Invoking `volume_render` front-end binding.') if parallel: return scatter_parallel(*args) else: return scatter(*args)
def getImage(parts, width, center, res): pos = parts["positions"] pos = pos - center + 0.5 * width pos /= width h = parts["smoothing_lengths"] / width m = np.ones(pos.shape[0]) # Do the projection img = vis.scatter(pos[:, 0], pos[:, 1], m, h, res).T img /= width**3 ind = img == 0 img[ind] = img[~ind].min() img = np.log10(img) return img
y_max = np.max(snapshot['PartType0']['Coordinates'][depth_mask, 1]) y_min = np.min(snapshot['PartType0']['Coordinates'][depth_mask, 1]) x_range = x_max - x_min y_range = y_max - y_min x = (snapshot['PartType0']['Coordinates'][depth_mask, 0] - x_min) / x_range y = (snapshot['PartType0']['Coordinates'][depth_mask, 1] - y_min) / y_range h = snapshot['PartType0']['SmoothingLength'][depth_mask] / x_range # Gather and handle coordinates to be processed x = np.asarray(x.value, dtype=np.float64) y = np.asarray(y.value, dtype=np.float64) m = np.asarray(snapshot['PartType0']['Density'][depth_mask].value, dtype=np.float32) h = np.asarray(h.value, dtype=np.float32) read.pprint(f'Computing map ({resolution} x {resolution})') smoothed_map = scatter(x=x, y=y, m=m, h=h, res=resolution).T smoothed_map = np.ma.masked_where( np.abs(smoothed_map) < 1.e-9, smoothed_map) fig, axes = plt.subplots() cmap = copy.copy(plt.get_cmap('twilight')) cmap.set_under('black') axes.axis("off") axes.set_aspect("equal") axes.imshow(smoothed_map.T, norm=LogNorm(), cmap=cmap, origin="lower", extent=[x_min, x_max, y_min, y_max]) fig.savefig('box.png')
def density_map(particle_type: int, cluster_data) -> None: z = cluster_data.header.subfind_particles.Redshift CoP = cluster_data.subfind_tab.FOF.GroupCentreOfPotential M200c = cluster_data.subfind_tab.FOF.Group_M_Crit200 R200c = cluster_data.subfind_tab.FOF.Group_R_Crit200 R500c = cluster_data.subfind_tab.FOF.Group_R_Crit500 M500c = cluster_data.subfind_tab.FOF.Group_M_Crit500 coord = cluster_data.subfind_particles[f'PartType{particle_type}'][ 'Coordinates'] boxsize = cluster_data.boxsize DM_part_mass = cluster_data.mass_DMpart if particle_type == 1: masses = np.ones_like(coord[:, 0].value, dtype=np.float32) * DM_part_mass # Generate DM particle smoothing lengths smoothing_lengths = generate_smoothing_lengths( coord, boxsize, kernel_gamma=1.8, neighbours=57, speedup_fac=3, dimension=3, ) else: masses = cluster_data.subfind_particles[f'PartType{particle_type}'][ 'Mass'] smoothing_lengths = cluster_data.subfind_particles[ f'PartType{particle_type}']['SmoothingLength'] # Run aperture filter read.pprint('[Check] Particle max x: ', np.max(np.abs(coord[:, 0] - CoP[0])), '6 x R500c: ', 6 * R500c) read.pprint('[Check] Particle max y: ', np.max(np.abs(coord[:, 1] - CoP[1])), '6 x R500c: ', 6 * R500c) read.pprint('[Check] Particle max z: ', np.max(np.abs(coord[:, 2] - CoP[2])), '6 x R500c: ', 6 * R500c) # Rotate particles # coord_rot = rotation_align_with_vector(coord.value, CoP, np.array([0, 0, 1])) coord_rot = coord # After derotation create a cubic aperture filter inscribed within a sphere of radius 5xR500c and # Centred in the CoP. Each semi-side of the aperture has length 5 * R500c / sqrt(3). aperture = 5 * R500c / np.sqrt(3) mask = np.where((np.abs(coord_rot[:, 0] - CoP[0]) <= aperture) & (np.abs(coord_rot[:, 1] - CoP[1]) <= aperture) & (np.abs(coord_rot[:, 2] - CoP[2]) <= aperture))[0] # Gather and handle coordinates to be plotted x = coord_rot[mask, 0].value y = coord_rot[mask, 1].value x_max = np.max(x) x_min = np.min(x) y_max = np.max(y) y_min = np.min(y) x_range = x_max - x_min y_range = y_max - y_min # Test that we've got a square box read.pprint(x_range, y_range) map_input_m = np.asarray(masses.value, dtype=np.float32) map_input_h = np.asarray(smoothing_lengths.value, dtype=np.float32) mass_map = scatter(x=(x - x_min) / x_range, y=(y - y_min) / y_range, m=map_input_m[mask], h=map_input_h[mask] / x_range, res=map_resolution) mass_map_units = masses.units / coord.units**2 # Mask zero values in the map with black mass_map = np.ma.masked_where(mass_map < 0.05, mass_map) read.pprint(mass_map) # Make figure fig, ax = plt.subplots(figsize=(6, 6), dpi=map_resolution // 6) ax.set_aspect('equal') fig.subplots_adjust(0, 0, 1, 1) ax.axis("off") ax.imshow(mass_map.T, norm=LogNorm(), cmap="inferno", origin="lower", extent=[x_min, x_max, y_min, y_max]) t = ax.text( 0.025, 0.025, (f"Halo {cluster_id:d} {simulation_type}\n" f"Particles: {partType_atlas[str(particle_type)]}\n" f"$z={z:3.3f}$\n" f"$M_{{500c}}={latex_float(M500c.value)}$ M$_\odot$\n" f"$R_{{500c}}={latex_float(R500c.value)}$ Mpc\n" f"$M_{{200c}}={latex_float(M200c.value)}$ M$_\odot$\n" f"$R_{{200c}}={latex_float(R200c.value)}$ Mpc"), color="white", ha="left", va="bottom", transform=ax.transAxes, ) t.set_bbox(dict(facecolor='black', alpha=0.2, edgecolor='none')) ax.text(CoP[0], CoP[1] + 1.02 * R500c, r"$R_{500c}$", color="white", ha="center", va="bottom") circle_r500 = plt.Circle((CoP[0], CoP[1]), R500c, color="white", fill=False, linestyle='-') ax.add_artist(circle_r500) plt.tight_layout() fig.savefig( f"{output_directory}/halo{cluster_id}_{redshift}_densitymap_type{particle_type}.png" ) plt.close(fig)