|
| 1 | +#!/usr/bin/env python |
| 2 | +""" |
| 3 | +Usage: |
| 4 | + combine_ics.py input_file.0.hdf5 merged_file.hdf5 gzip_level |
| 5 | +
|
| 6 | +This file combines Gadget-2 type 2 (i.e. hdf5) initial condition files |
| 7 | +into a single file that can be digested by SWIFT. |
| 8 | +This has mainly be tested for DM-only (parttype1) files but also works |
| 9 | +smoothly for ICs including gas. The special case of a mass-table for |
| 10 | +the DM particles is handled. No unit conversions are applied nor are |
| 11 | +any scale-factors or h-factors changed. |
| 12 | +The script applies some compression and checksum filters to the output |
| 13 | +to save disk space. |
| 14 | +The last argument `gzip_level` is used to specify the level of compression |
| 15 | +to apply to all the fields in the file. Use 0 to cancel all coompression. |
| 16 | +The default value is `4`. |
| 17 | +
|
| 18 | +This file is adapted from a part of SWIFT. |
| 19 | +Copyright (C) 2016 Matthieu Schaller ([email protected]) |
| 20 | +
|
| 21 | +All Rights Reserved. |
| 22 | +
|
| 23 | +This program is free software: you can redistribute it and/or modify |
| 24 | +it under the terms of the GNU Lesser General Public License as published |
| 25 | +by the Free Software Foundation, either version 3 of the License, or |
| 26 | +(at your option) any later version. |
| 27 | +
|
| 28 | +This program is distributed in the hope that it will be useful, |
| 29 | +but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 30 | +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 31 | +GNU General Public License for more details. |
| 32 | +
|
| 33 | +You should have received a copy of the GNU Lesser General Public License |
| 34 | +along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 35 | +""" |
| 36 | + |
| 37 | +from re import T |
| 38 | +import sys |
| 39 | +import h5py as h5 |
| 40 | +import numpy as np |
| 41 | + |
| 42 | +# Store the compression level |
| 43 | +gzip_level = 4 |
| 44 | +if len(sys.argv) > 3: |
| 45 | + gzip_level = int(sys.argv[3]) |
| 46 | + |
| 47 | +# First, we need to collect some information from the master file |
| 48 | +main_file_name = str(sys.argv[1])[:-7] |
| 49 | +print("Merging snapshots files with name", main_file_name) |
| 50 | +master_file_name = main_file_name + ".0.hdf5" |
| 51 | +print("Reading master information from", master_file_name) |
| 52 | +master_file = h5.File(master_file_name, "r") |
| 53 | +grp_header = master_file["/Header"] |
| 54 | + |
| 55 | +num_files = grp_header.attrs["NumFilesPerSnapshot"] |
| 56 | +tot_num_parts = grp_header.attrs["NumPart_Total"] |
| 57 | +tot_num_parts_high_word = grp_header.attrs["NumPart_Total_HighWord"] |
| 58 | +box_size = grp_header.attrs["BoxSize"] |
| 59 | +time = grp_header.attrs["Time"] |
| 60 | +hubble_param = grp_header.attrs['HubbleParam'] |
| 61 | +omega0 = grp_header.attrs['Omega0'] |
| 62 | +omegaLambda = grp_header.attrs['OmegaLambda'] |
| 63 | + |
| 64 | +# Combine the low- and high-words |
| 65 | +tot_num_parts = tot_num_parts.astype(np.int64) |
| 66 | +for i in range(6): |
| 67 | + tot_num_parts[i] += np.int64(tot_num_parts_high_word[i]) << 32 |
| 68 | + |
| 69 | +tot_num_parts_swift = np.copy(tot_num_parts) |
| 70 | +tot_num_parts_swift[2] += tot_num_parts_swift[3] |
| 71 | +tot_num_parts_swift[3] = 0 |
| 72 | + |
| 73 | +# Some basic information |
| 74 | +print("Reading", tot_num_parts, "particles from", num_files, "files.") |
| 75 | + |
| 76 | +# Check whether there is a mass table |
| 77 | +DM_mass = 0.0 |
| 78 | +mtable = grp_header.attrs.get("MassTable") |
| 79 | +if mtable is not None: |
| 80 | + DM_mass = grp_header.attrs["MassTable"][1] / hubble_param |
| 81 | +if DM_mass != 0.0: |
| 82 | + print("DM mass set to", DM_mass, "from the header mass table.") |
| 83 | +else: |
| 84 | + print("Reading DM mass from the particles.") |
| 85 | + |
| 86 | + |
| 87 | +# Create the empty file |
| 88 | +output_file_name = sys.argv[2] |
| 89 | +output_file = h5.File(output_file_name, "w-") |
| 90 | + |
| 91 | + |
| 92 | +# Header |
| 93 | +grp = output_file.create_group("/Header") |
| 94 | +grp.attrs["NumFilesPerSnapshot"] = 1 |
| 95 | +grp.attrs["NumPart_Total"] = tot_num_parts_swift |
| 96 | +grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0] |
| 97 | +grp.attrs["NumPart_ThisFile"] = tot_num_parts_swift |
| 98 | +grp.attrs["MassTable"] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] |
| 99 | +grp.attrs["BoxSize"] = box_size / hubble_param |
| 100 | +grp.attrs["Flag_Entropy_ICs"] = 0 |
| 101 | +grp.attrs["Time"] = time |
| 102 | +grp.attrs['HubbleParam'] = hubble_param |
| 103 | +grp.attrs['Omega0'] = omega0 |
| 104 | +grp.attrs['OmegaLambda'] = omegaLambda |
| 105 | + |
| 106 | + |
| 107 | +# Create the particle groups |
| 108 | +if tot_num_parts[0] > 0: |
| 109 | + grp0 = output_file.create_group("/PartType0") |
| 110 | +if tot_num_parts[1] > 0: |
| 111 | + grp1 = output_file.create_group("/PartType1") |
| 112 | +if tot_num_parts_swift[2] > 0: |
| 113 | + grp2 = output_file.create_group("/PartType2") |
| 114 | +if tot_num_parts[4] > 0: |
| 115 | + grp4 = output_file.create_group("/PartType4") |
| 116 | +if tot_num_parts[5] > 0: |
| 117 | + grp5 = output_file.create_group("/PartType5") |
| 118 | + |
| 119 | + |
| 120 | +# Helper function to create the datasets we need |
| 121 | +def create_set(grp, name, size, dim, dtype): |
| 122 | + if dim == 1: |
| 123 | + grp.create_dataset( |
| 124 | + name, |
| 125 | + (size,), |
| 126 | + dtype=dtype, |
| 127 | + chunks=True, |
| 128 | + compression="gzip", |
| 129 | + compression_opts=gzip_level, |
| 130 | + shuffle=True, |
| 131 | + fletcher32=True, |
| 132 | + maxshape=(size,), |
| 133 | + ) |
| 134 | + else: |
| 135 | + grp.create_dataset( |
| 136 | + name, |
| 137 | + (size, dim), |
| 138 | + dtype=dtype, |
| 139 | + chunks=True, |
| 140 | + compression="gzip", |
| 141 | + compression_opts=gzip_level, |
| 142 | + shuffle=True, |
| 143 | + fletcher32=True, |
| 144 | + maxshape=(size, dim), |
| 145 | + ) |
| 146 | + |
| 147 | + |
| 148 | +# Create the required datasets |
| 149 | +if tot_num_parts[0] > 0: |
| 150 | + create_set(grp0, "Coordinates", tot_num_parts[0], 3, "d") |
| 151 | + create_set(grp0, "Velocities", tot_num_parts[0], 3, "f") |
| 152 | + create_set(grp0, "Masses", tot_num_parts[0], 1, "f") |
| 153 | + create_set(grp0, "ParticleIDs", tot_num_parts[0], 1, "l") |
| 154 | + create_set(grp0, "InternalEnergy", tot_num_parts[0], 1, "f") |
| 155 | + create_set(grp0, "SmoothingLength", tot_num_parts[0], 1, "f") |
| 156 | + create_set(grp0, 'Potential', tot_num_parts[0], 1, 'f') |
| 157 | + |
| 158 | +if tot_num_parts[1] > 0: |
| 159 | + create_set(grp1, "Coordinates", tot_num_parts[1], 3, "d") |
| 160 | + create_set(grp1, "Velocities", tot_num_parts[1], 3, "f") |
| 161 | + create_set(grp1, "Masses", tot_num_parts[1], 1, "f") |
| 162 | + create_set(grp1, "ParticleIDs", tot_num_parts[1], 1, "l") |
| 163 | + create_set(grp1, 'Potential', tot_num_parts[1], 1, 'f') |
| 164 | + |
| 165 | +if tot_num_parts_swift[2] > 0: |
| 166 | + create_set(grp2, "Coordinates", tot_num_parts_swift[2], 3, "d") |
| 167 | + create_set(grp2, "Velocities", tot_num_parts_swift[2], 3, "f") |
| 168 | + create_set(grp2, "Masses", tot_num_parts_swift[2], 1, "f") |
| 169 | + create_set(grp2, "ParticleIDs", tot_num_parts_swift[2], 1, "l") |
| 170 | + create_set(grp2, 'Potential', tot_num_parts_swift[2], 1, 'f') |
| 171 | + |
| 172 | +if tot_num_parts[4] > 0: |
| 173 | + create_set(grp4, "Coordinates", tot_num_parts[4], 3, "d") |
| 174 | + create_set(grp4, "Velocities", tot_num_parts[4], 3, "f") |
| 175 | + create_set(grp4, "Masses", tot_num_parts[4], 1, "f") |
| 176 | + create_set(grp4, "ParticleIDs", tot_num_parts[4], 1, "l") |
| 177 | + create_set(grp4, 'Potential', tot_num_parts[4], 1, 'f') |
| 178 | + |
| 179 | +if tot_num_parts[5] > 0: |
| 180 | + create_set(grp5, "Coordinates", tot_num_parts[5], 3, "d") |
| 181 | + create_set(grp5, "Velocities", tot_num_parts[5], 3, "f") |
| 182 | + create_set(grp5, "Masses", tot_num_parts[5], 1, "f") |
| 183 | + create_set(grp5, "ParticleIDs", tot_num_parts[5], 1, "l") |
| 184 | + create_set(grp5, 'Potential', tot_num_parts[5], 1, 'f') |
| 185 | + |
| 186 | +# Heavy-lifting ahead. Leave a last message. |
| 187 | +print("Datasets created in output file") |
| 188 | + |
| 189 | + |
| 190 | +# Special case of the non-zero mass table |
| 191 | +if DM_mass != 0.0: |
| 192 | + masses = np.ones(tot_num_parts[1], dtype=float) * DM_mass |
| 193 | + grp1["Masses"][:] = masses |
| 194 | + |
| 195 | + |
| 196 | +# Cumulative number of particles read/written |
| 197 | +cumul_parts = [0, 0, 0, 0, 0, 0] |
| 198 | + |
| 199 | +# Loop over all the files that are part of the snapshots |
| 200 | +for f in range(num_files): |
| 201 | + |
| 202 | + file_name = main_file_name + "." + str(f) + ".hdf5" |
| 203 | + file = h5.File(file_name, "r") |
| 204 | + file_header = file["/Header"] |
| 205 | + num_parts = file_header.attrs["NumPart_ThisFile"] |
| 206 | + |
| 207 | + print( |
| 208 | + "Copying data from file", |
| 209 | + f, |
| 210 | + "/", |
| 211 | + num_files, |
| 212 | + ": num_parts = [", |
| 213 | + num_parts[0], |
| 214 | + num_parts[1], |
| 215 | + num_parts[2], |
| 216 | + num_parts[3], |
| 217 | + num_parts[4], |
| 218 | + num_parts[5], |
| 219 | + "]", |
| 220 | + ) |
| 221 | + sys.stdout.flush() |
| 222 | + |
| 223 | + # Helper function to copy data |
| 224 | + def copy_grp(name_new, name_old, ptype, correct_h): |
| 225 | + full_name_new = "/PartType" + str(ptype) + "/" + name_new |
| 226 | + full_name_old = "/PartType" + str(ptype) + "/" + name_old |
| 227 | + if correct_h: |
| 228 | + output_file[full_name_new][cumul_parts[ptype] : cumul_parts[ptype] + num_parts[ptype]] = file[full_name_old] / hubble_param |
| 229 | + else: |
| 230 | + output_file[full_name_new][cumul_parts[ptype] : cumul_parts[ptype] + num_parts[ptype]] = file[full_name_old] |
| 231 | + |
| 232 | + def copy_grp_same_name(name, ptype, correct_h): |
| 233 | + copy_grp(name, name, ptype, correct_h) |
| 234 | + |
| 235 | + def copy_grp_pt3(name, correct_h): |
| 236 | + full_name_new = "/PartType2/" + name |
| 237 | + full_name_old = "/PartType3/" + name |
| 238 | + if correct_h: |
| 239 | + output_file[full_name_new][cumul_parts[2] : cumul_parts[2] + num_parts[3]] = file[full_name_old] / hubble_param |
| 240 | + else: |
| 241 | + output_file[full_name_new][cumul_parts[2] : cumul_parts[2] + num_parts[3]] = file[full_name_old] |
| 242 | + |
| 243 | + if num_parts[0] > 0: |
| 244 | + copy_grp_same_name("Coordinates", 0, True) |
| 245 | + copy_grp_same_name("Velocities", 0, False) |
| 246 | + copy_grp_same_name("Masses", 0, False) |
| 247 | + copy_grp_same_name("ParticleIDs", 0, False) |
| 248 | + copy_grp_same_name("InternalEnergy", 0, False) |
| 249 | + copy_grp_same_name("SmoothingLength", 0, False) |
| 250 | + copy_grp_same_name('Potential', 0, False) ######## Careful: I don't actually know the units of the Potential, so I don't know if I should correct it. |
| 251 | + |
| 252 | + if num_parts[1] > 0: |
| 253 | + copy_grp_same_name("Coordinates", 1, True) |
| 254 | + copy_grp_same_name("Velocities", 1, False) |
| 255 | + copy_grp_same_name("ParticleIDs", 1, False) |
| 256 | + if DM_mass == 0.0: # Do not overwrite values if there was a mass table |
| 257 | + copy_grp_same_name("Masses", 1, False) |
| 258 | + copy_grp_same_name('Potential', 1, False) |
| 259 | + |
| 260 | + if num_parts[2] > 0: |
| 261 | + copy_grp_same_name("Coordinates", 2, True) |
| 262 | + copy_grp_same_name("Velocities", 2, False) |
| 263 | + copy_grp_same_name("ParticleIDs", 2, False) |
| 264 | + copy_grp_same_name("Masses", 2, False) |
| 265 | + copy_grp_same_name('Potential', 2, False) |
| 266 | + |
| 267 | + # Need to update part counter for pt2 already here, so we append 3 in correct place |
| 268 | + cumul_parts[2] += num_parts[2] |
| 269 | + |
| 270 | + if num_parts[3] > 0: |
| 271 | + copy_grp_pt3("Coordinates", True) |
| 272 | + copy_grp_pt3("Velocities", False) |
| 273 | + copy_grp_pt3("ParticleIDs", False) |
| 274 | + copy_grp_pt3("Masses", False) |
| 275 | + copy_grp_pt3('Potential', False) |
| 276 | + |
| 277 | + if num_parts[4] > 0: |
| 278 | + copy_grp_same_name("Coordinates", 4, True) |
| 279 | + copy_grp_same_name("Velocities", 4, False) |
| 280 | + copy_grp_same_name("Masses", 4, False) |
| 281 | + copy_grp_same_name("ParticleIDs", 4, False) |
| 282 | + copy_grp_same_name('Potential', 4, False) |
| 283 | + |
| 284 | + if num_parts[5] > 0: |
| 285 | + copy_grp_same_name("Coordinates", 5, True) |
| 286 | + copy_grp_same_name("Velocities", 5, False) |
| 287 | + copy_grp_same_name("Masses", 5, False) |
| 288 | + copy_grp_same_name("ParticleIDs", 5, False) |
| 289 | + copy_grp_same_name('Potential', 5, False) |
| 290 | + |
| 291 | + cumul_parts[0] += num_parts[0] |
| 292 | + cumul_parts[1] += num_parts[1] |
| 293 | + cumul_parts[2] += num_parts[3] # Need to adjust for added pt-3 |
| 294 | + cumul_parts[4] += num_parts[4] |
| 295 | + cumul_parts[5] += num_parts[5] |
| 296 | + file.close() |
| 297 | + |
| 298 | +print("All done! SWIFT is waiting.") |
0 commit comments