def _velocity(field, data, idir, prefix=None): """Velocity = linear momentum / density""" # This is meant to be used with functools.partial to produce # functions with only 2 arguments (field, data) # idir : int # the direction index (1, 2 or 3) # prefix : str # used to generalize to dust fields if prefix is None: prefix = "" moment = data["gas", "%smoment_%d" % (prefix, idir)] rho = data["gas", f"{prefix}density"] mask1 = rho == 0 if mask1.any(): mylog.info( "zeros found in %sdensity, " "patching them to compute corresponding velocity field.", prefix, ) mask2 = moment == 0 if not ((mask1 & mask2) == mask1).all(): raise RuntimeError rho[mask1] = 1 return moment / rho
def check_model(self): n = len(self.ee) rho = np.zeros(n) rho_int = lambda e, psi: self.f(e)*np.sqrt(2*(psi-e)) for i, e in enumerate(self.ee[::-1]): rho[i] = 4.*np.pi*quad(rho_int, 0., e, args=(e))[0] chk = np.abs(rho-self.pden)/self.pden mylog.info("The maximum relative deviation of this profile from " "virial equilibrium is %g" % np.abs(chk).max()) return rho, chk
def from_scratch(cls, num_particles, rmin, rmax, profile, num_points=1000): rr = np.logspace(np.log10(rmin), np.log10(rmax), num_points, endpoint=True) pden = profile(rr) mylog.info("Integrating dark matter mass profile.") mdm = integrate_mass(profile, rr) mylog.info("Integrating gravitational potential profile.") gpot_profile = lambda r: profile(r)*r gpot = G.v*(mdm/rr + 4.*np.pi*integrate_toinf(gpot_profile, rr)) return cls(num_particles, rr, gpot, pden, mdm)
def check_model(self): r""" Determine the deviation of the model from hydrostatic equilibrium. Returns an array containing the relative deviation at each radius or height. """ xx = self.fields[self.x_field].v pressure_spline = InterpolatedUnivariateSpline(xx, self.fields["pressure"].v) dPdx = YTArray(pressure_spline(xx, 1), "Msun/(Myr**2*kpc**2)") rhog = self.fields["density"] * self.fields["gravitational_field"] chk = dPdx - rhog chk /= rhog mylog.info( "The maximum relative deviation of this profile from " "hydrostatic equilibrium is %g" % np.abs(chk).max() ) return chk
def setup_fluid_fields(self): setup_magnetic_field_aliases(self, "amrvac", [f"mag{ax}" for ax in "xyz"]) self._setup_velocity_fields() # gas velocities self._setup_dust_fields() # dust derived fields (including velocities) # fields with nested dependencies are defined thereafter # by increasing level of complexity us = self.ds.unit_system def _kinetic_energy_density(field, data): # devnote : have a look at issue 1301 return 0.5 * data["gas", "density"] * data["gas", "velocity_magnitude"]**2 self.add_field( ("gas", "kinetic_energy_density"), function=_kinetic_energy_density, units=us["density"] * us["velocity"]**2, dimensions=dimensions.density * dimensions.velocity**2, sampling_type="cell", ) # magnetic energy density if ("amrvac", "b1") in self.field_list: def _magnetic_energy_density(field, data): emag = 0.5 * data["gas", "magnetic_1"]**2 for idim in "23": if not ("amrvac", f"b{idim}") in self.field_list: break emag += 0.5 * data["gas", f"magnetic_{idim}"]**2 # in AMRVAC the magnetic field is defined in units where mu0 = 1, # such that # Emag = 0.5*B**2 instead of Emag = 0.5*B**2 / mu0 # To correctly transform the dimensionality from gauss**2 -> rho*v**2, # we have to take mu0 into account. If we divide here, units when adding # the field should be us["density"]*us["velocity"]**2. # If not, they should be us["magnetic_field"]**2 and division should # happen elsewhere. emag /= 4 * np.pi # divided by mu0 = 4pi in cgs, # yt handles 'mks' and 'code' unit systems internally. return emag self.add_field( ("gas", "magnetic_energy_density"), function=_magnetic_energy_density, units=us["density"] * us["velocity"]**2, dimensions=dimensions.density * dimensions.velocity**2, sampling_type="cell", ) # Adding the thermal pressure field. # In AMRVAC we have multiple physics possibilities: # - if HD/MHD + energy equation P = (gamma-1)*(e - ekin (- emag)) for (M)HD # - if HD/MHD but solve_internal_e is true in parfile, P = (gamma-1)*e for both # - if (m)hd_energy is false in parfile (isothermal), P = c_adiab * rho**gamma def _full_thermal_pressure_HD(field, data): # energy density and pressure are actually expressed in the same unit pthermal = (data.ds.gamma - 1) * (data["gas", "energy_density"] - data["gas", "kinetic_energy_density"]) return pthermal def _full_thermal_pressure_MHD(field, data): pthermal = ( _full_thermal_pressure_HD(field, data) - (data.ds.gamma - 1) * data["gas", "magnetic_energy_density"]) return pthermal def _polytropic_thermal_pressure(field, data): return (data.ds.gamma - 1) * data["gas", "energy_density"] def _adiabatic_thermal_pressure(field, data): return data.ds._c_adiab * data["gas", "density"]**data.ds.gamma pressure_recipe = None if ("amrvac", "e") in self.field_list: if self.ds._e_is_internal: pressure_recipe = _polytropic_thermal_pressure mylog.info("Using polytropic EoS for thermal pressure.") elif ("amrvac", "b1") in self.field_list: pressure_recipe = _full_thermal_pressure_MHD mylog.info("Using full MHD energy for thermal pressure.") else: pressure_recipe = _full_thermal_pressure_HD mylog.info("Using full HD energy for thermal pressure.") elif self.ds._c_adiab is not None: pressure_recipe = _adiabatic_thermal_pressure mylog.info( "Using adiabatic EoS for thermal pressure (isothermal).") mylog.warning("If you used usr_set_pthermal you should " "redefine the thermal_pressure field.") if pressure_recipe is not None: self.add_field( ("gas", "thermal_pressure"), function=pressure_recipe, units=us["density"] * us["velocity"]**2, dimensions=dimensions.density * dimensions.velocity**2, sampling_type="cell", ) # sound speed and temperature depend on thermal pressure def _sound_speed(field, data): return np.sqrt(data.ds.gamma * data["gas", "thermal_pressure"] / data["gas", "density"]) self.add_field( ("gas", "sound_speed"), function=_sound_speed, units=us["velocity"], dimensions=dimensions.velocity, sampling_type="cell", ) else: mylog.warning( "e not found and no parfile passed, can not set thermal_pressure." )
def save(obj, filename='test.hdf5'): """Function to save a CAESAR file to disk. Parameters ---------- obj : :class:`main.CAESAR` Main caesar object to save. filename : str, optional Filename of the output file. Examples -------- >>> obj.save('output.hdf5') """ from yt.funcs import mylog if os.path.isfile(filename): mylog.warning('%s already present, overwriting!' % filename) os.remove(filename) mylog.info('Writing %s' % filename) outfile = h5py.File(filename, 'w') outfile.attrs.create('caesar', 315) unit_registry = obj.yt_dataset.unit_registry.to_json() outfile.attrs.create('unit_registry_json', unit_registry.encode('utf8')) serialize_global_attribs(obj, outfile) obj.simulation._serialize(obj, outfile) if hasattr(obj, 'halos') and obj.nhalos > 0: hd = outfile.create_group('halo_data') hdd = hd.create_group('lists') hddd = hd.create_group('dicts') # gather index_lists = ['dmlist'] if 'gas' in obj.data_manager.ptypes: index_lists.append('glist') if 'star' in obj.data_manager.ptypes: index_lists.extend(['slist', 'galaxy_index_list']) if 'dm2' in obj.data_manager.ptypes: index_lists.append('dm2list') if 'dm3' in obj.data_manager.ptypes: index_lists.append('dm3list') if obj.data_manager.blackholes: index_lists.append('bhlist') if obj.data_manager.dust: index_lists.append('dlist') #write for vals in index_lists: serialize_list(obj.halos, vals, hdd) serialize_attributes(obj.halos, hd, hddd) if hasattr(obj, 'galaxies') and obj.ngalaxies > 0: hd = outfile.create_group('galaxy_data') hdd = hd.create_group('lists') hddd = hd.create_group('dicts') # gather index_lists = ['glist', 'slist', 'cloud_index_list'] if obj.data_manager.blackholes: index_lists.append('bhlist') if obj.data_manager.dust: index_lists.append('dlist') # write for vals in index_lists: serialize_list(obj.galaxies, vals, hdd) serialize_attributes(obj.galaxies, hd, hddd) if hasattr(obj, 'clouds') and obj.nclouds > 0: hd = outfile.create_group('cloud_data') hdd = hd.create_group('lists') hddd = hd.create_group('dicts') # gather index_lists = ['glist'] # write for vals in index_lists: serialize_list(obj.clouds, vals, hdd) serialize_attributes(obj.clouds, hd, hddd) if hasattr(obj, 'global_particle_lists'): hd = outfile.create_group('global_lists') # gather global_index_lists = [ 'halo_dmlist', 'halo_glist', 'halo_slist', 'galaxy_glist', 'galaxy_slist', 'cloud_glist' ] if obj.data_manager.blackholes: global_index_lists.extend(['halo_bhlist', 'galaxy_bhlist']) if obj.data_manager.dust: global_index_lists.extend(['halo_dlist', 'galaxy_dlist']) # write for vals in global_index_lists: check_and_write_dataset(obj.global_particle_lists, vals, hd) outfile.close()
def __init__(self, num_particles, rr, gpot, pden, mdm): fields = OrderedDict() ee = gpot[::-1] density_spline = InterpolatedUnivariateSpline(ee, pden[::-1]) energy_spline = InterpolatedUnivariateSpline(rr, gpot) num_points = gpot.shape[0] g = np.zeros(num_points) dgdp = lambda t, e: 2*density_spline(e-t*t, 1) pbar = get_pbar("Computing particle DF.", num_points) for i in range(num_points): g[i] = quad(dgdp, 0., np.sqrt(ee[i]), args=(ee[i]))[0] pbar.update(i) pbar.finish() g_spline = InterpolatedUnivariateSpline(ee, g) f = lambda e: g_spline(e, 1)/(np.sqrt(8.)*np.pi**2) self.ee = ee self.f = f self.rr = rr self.pden = pden mylog.info("We will be assigning %d particles." % num_particles) mylog.info("Compute particle positions.") u = np.random.uniform(size=num_particles) P_r = np.insert(mdm, 0, 0.0) P_r /= P_r[-1] radius = np.interp(u, P_r, np.insert(rr, 0, 0.0), left=0.0, right=1.0) theta = np.arccos(np.random.uniform(low=-1.,high=1.,size=num_particles)) phi = 2.*np.pi*np.random.uniform(size=num_particles) fields["particle_radius"] = YTArray(radius, "kpc") fields["particle_position_x"] = YTArray(radius*np.sin(theta)*np.cos(phi), "kpc") fields["particle_position_y"] = YTArray(radius*np.sin(theta)*np.sin(phi), "kpc") fields["particle_position_z"] = YTArray(radius*np.cos(theta), "kpc") mylog.info("Compute particle velocities.") psi = energy_spline(radius) vesc = 2.*psi fv2esc = vesc*f(psi) vesc = np.sqrt(vesc) velocity = generate_velocities(psi, vesc, fv2esc, f) theta = np.arccos(np.random.uniform(low=-1.,high=1.,size=num_particles)) phi = 2.*np.pi*np.random.uniform(size=num_particles) fields["particle_velocity"] = YTArray(velocity, "kpc/Myr") fields["particle_velocity_x"] = YTArray(velocity*np.sin(theta)*np.cos(phi), "kpc/Myr") fields["particle_velocity_y"] = YTArray(velocity*np.sin(theta)*np.sin(phi), "kpc/Myr") fields["particle_velocity_z"] = YTArray(velocity*np.cos(theta), "kpc/Myr") fields["particle_mass"] = YTArray([mdm.max()/num_particles], "Msun") fields["particle_potential"] = YTArray(psi, "kpc**2/Myr**2") fields["particle_energy"] = fields["particle_potential"]-0.5*fields["particle_velocity"]**2 super(VirialEquilibrium, self).__init__(num_particles, fields, "spherical")
def from_scratch( cls, mode, xmin, xmax, profiles, input_units=None, num_points=1000, geometry="spherical", P_amb=0.0 ): r""" Generate a set of profiles of physical quantities based on the assumption of hydrostatic equilibrium. Currently assumes an ideal gas with a gamma-law equation of state. Parameters ---------- mode : string The method to generate the profiles from an initial set. Can be one of the following: "dens_temp": Generate the profiles given a gas density and gas temperature profile. "dens_tden": Generate the profiles given a gas density and total density profile. "dens_grav": Generate the profiles given a gas density and gravitational acceleration profile. "dm_only": Generate profiles of gravitational potential and acceleration assuming an initial DM density profile. xmin : float The minimum radius or height for the profiles, assumed to be in kpc. xmax : float The maximum radius or height for the profiles, assumed to be in kpc. profiles : dict of functions A dictionary of callable functions of radius or height which return quantities such as density, total density, and so on. The functions are not unit-aware for speed purposes, but they assume that the base units are: "length": "kpc" "time": "Myr" "mass": "Msun" "temperature": "keV" parameters : dict of floats A dictionary of parameters needed for the calculation, which include: "mu": The mean molecular weight of the gas. Default is to assume a primordial H/He gas. "gamma": The ratio of specific heats. Default: 5/3. num_points : integer The number of points at which to evaluate the profile. geometry : string The geometry of the model. Can be "cartesian" or "spherical", which will determine whether or not the profiles are of "radius" or "height". """ if not isinstance(P_amb, YTQuantity): P_amb = YTQuantity(P_amb, "erg/cm**3") P_amb.convert_to_units("Msun/(Myr**2*kpc)") for p in modes[mode]: if p not in profiles: raise RequiredProfilesError(mode) if mode in ["dens_tden", "dm_only"] and geometry != "spherical": raise NotImplemented( "Constructing a HydrostaticEquilibrium from gas density and/or " "total density profiles is only allowed in spherical geometry!" ) extra_fields = [field for field in profiles if field not in cls.default_fields] if geometry == "cartesian": x_field = "height" elif geometry == "spherical": x_field = "radius" fields = OrderedDict() xx = np.logspace(np.log10(xmin), np.log10(xmax), num_points, endpoint=True) fields[x_field] = YTArray(xx, "kpc") if mode == "dm_only": fields["density"] = YTArray(np.zeros(num_points), "Msun/kpc**3") fields["pressure"] = YTArray(np.zeros(num_points), "Msun/kpc/Myr**2") fields["temperature"] = YTArray(np.zeros(num_points), "keV") else: fields["density"] = YTArray(profiles["density"](xx), "Msun/kpc**3") if mode == "dens_temp": mylog.info("Computing the profiles from density and temperature.") fields["temperature"] = YTArray(profiles["temperature"](xx), "keV") fields["pressure"] = fields["density"] * fields["temperature"] fields["pressure"] *= muinv / mp fields["pressure"].convert_to_units("Msun/(Myr**2*kpc)") pressure_spline = InterpolatedUnivariateSpline(xx, fields["pressure"].v) dPdx = YTArray(pressure_spline(xx, 1), "Msun/(Myr**2*kpc**2)") fields["gravitational_field"] = dPdx / fields["density"] fields["gravitational_field"].convert_to_units("kpc/Myr**2") else: if mode == "dens_tden" or mode == "dm_only": mylog.info("Computing the profiles from density and total density.") fields["total_density"] = YTArray(profiles["total_density"](xx), "Msun/kpc**3") mylog.info("Integrating total mass profile.") fields["total_mass"] = YTArray(integrate_mass(profiles["total_density"], xx), "Msun") fields["gravitational_field"] = -G * fields["total_mass"] / (fields["radius"] ** 2) fields["gravitational_field"].convert_to_units("kpc/Myr**2") elif mode == "dens_grav": mylog.info("Computing the profiles from density and gravitational acceleration.") fields["gravitational_field"] = YTArray(profiles["gravitational_field"](xx), "kpc/Myr**2") if mode != "dm_only": g = fields["gravitational_field"].in_units("kpc/Myr**2").v g_r = InterpolatedUnivariateSpline(xx, g) dPdr_int = lambda r: profiles["density"](r) * g_r(r) mylog.info("Integrating pressure profile.") fields["pressure"] = -YTArray(integrate(dPdr_int, xx), "Msun/kpc/Myr**2") fields["temperature"] = fields["pressure"] * mp / fields["density"] / muinv fields["temperature"].convert_to_units("keV") if geometry == "spherical": if "total_mass" not in fields: fields["total_mass"] = -fields["radius"] ** 2 * fields["gravitational_field"] / G if "total_density" not in fields: total_mass_spline = InterpolatedUnivariateSpline(xx, fields["total_mass"].v) dMdr = YTArray(total_mass_spline(xx, 1), "Msun/kpc") fields["total_density"] = dMdr / (4.0 * np.pi * fields["radius"] ** 2) mylog.info("Integrating gravitational potential profile.") if "total_density" in profiles: tdens_func = profiles["total_density"] else: tdens_func = InterpolatedUnivariateSpline(xx, fields["total_density"].d) gpot_profile = lambda r: tdens_func(r) * r gpot = YTArray(4.0 * np.pi * integrate(gpot_profile, xx), "Msun/kpc") fields["gravitational_potential"] = -G * (fields["total_mass"] / fields["radius"] + gpot) fields["gravitational_potential"].convert_to_units("kpc**2/Myr**2") if mode != "dm_only": mylog.info("Integrating gas mass profile.") fields["gas_mass"] = YTArray(integrate_mass(profiles["density"], xx), "Msun") mdm = fields["total_mass"].copy() ddm = fields["total_density"].copy() if mode != "dm_only": mdm -= fields["gas_mass"] ddm -= fields["density"] mdm[ddm.v < 0.0][:] = mdm.max() ddm[ddm.v < 0.0][:] = 0.0 fields["dark_matter_density"] = ddm fields["dark_matter_mass"] = mdm fields["pressure"] += P_amb for field in extra_fields: fields[field] = profiles[field](xx) return cls(num_points, fields, geometry)
def setup_fluid_fields(self): def _v1(field, data): return data["gas", "moment_1"] / data["gas", "density"] def _v2(field, data): return data["gas", "moment_2"] / data["gas", "density"] def _v3(field, data): return data["gas", "moment_3"] / data["gas", "density"] us = self.ds.unit_system aliases = direction_aliases[self.ds.geometry] for idir, alias, func in zip("123", aliases, (_v1, _v2, _v3)): if not ("amrvac", "m%s" % idir) in self.field_list: break self.add_field(("gas", "velocity_%s" % alias), function=func, units=us['velocity'], dimensions=dimensions.velocity, sampling_type="cell") self.alias(("gas", "velocity_%s" % idir), ("gas", "velocity_%s" % alias), units=us["velocity"]) self.alias(("gas", "moment_%s" % alias), ("gas", "moment_%s" % idir), units=us["density"] * us["velocity"]) setup_magnetic_field_aliases(self, "amrvac", ["mag%s" % ax for ax in "xyz"]) # fields with nested dependencies are defined thereafter by increasing level of complexity # kinetic pressure is given by 0.5 * rho * v**2 def _kinetic_energy_density(field, data): # devnote : have a look at issue 1301 return 0.5 * data['gas', 'density'] * data['gas', 'velocity_magnitude']**2 self.add_field(("gas", "kinetic_energy_density"), function=_kinetic_energy_density, units=us["density"] * us["velocity"]**2, dimensions=dimensions.density * dimensions.velocity**2, sampling_type="cell") # magnetic energy density if ('amrvac', 'b1') in self.field_list: def _magnetic_energy_density(field, data): emag = 0.5 * data['gas', 'magnetic_1']**2 for idim in '23': if not ('amrvac', 'b%s' % idim) in self.field_list: break emag += 0.5 * data['gas', 'magnetic_%s' % idim]**2 # important note: in AMRVAC the magnetic field is defined in units where mu0 = 1, such that # Emag = 0.5*B**2 instead of Emag = 0.5*B**2 / mu0 # To correctly transform the dimensionality from gauss**2 -> rho*v**2, we have to take mu0 into account. # If we divide here, units when adding the field should be us["density"]*us["velocity"]**2 # If not, they should be us["magnetic_field"]**2 and division should happen elsewhere. emag /= 4 * np.pi # divided by mu0 = 4pi in cgs, yt handles 'mks' and 'code' unit systems internally. return emag self.add_field( ('gas', 'magnetic_energy_density'), function=_magnetic_energy_density, units=us["density"] * us["velocity"]**2, dimensions=dimensions.density * dimensions.velocity**2, sampling_type='cell') # Adding the thermal pressure field. # In AMRVAC we have multiple physics possibilities: # - if HD/MHD + energy equation, pressure is (gamma-1)*(e - ekin (- emag)) for (M)HD # - if HD/MHD but solve_internal_e is true in parfile, pressure is (gamma-1)*e for both # - if (m)hd_energy is false in parfile (isothermal), pressure is c_adiab * rho**gamma def _full_thermal_pressure_HD(field, data): # important note : energy density and pressure are actually expressed in the same unit pthermal = (data.ds.gamma - 1) * (data['gas', 'energy_density'] - data['gas', 'kinetic_energy_density']) return pthermal def _full_thermal_pressure_MHD(field, data): pthermal = _full_thermal_pressure_HD(field, data) \ - (data.ds.gamma - 1) * data["gas", "magnetic_energy_density"] return pthermal def _polytropic_thermal_pressure(field, data): return (data.ds.gamma - 1) * data['gas', 'energy_density'] def _adiabatic_thermal_pressure(field, data): return data.ds._c_adiab * data["gas", "density"]**data.ds.gamma pressure_recipe = None if ("amrvac", "e") in self.field_list: if self.ds._e_is_internal: pressure_recipe = _polytropic_thermal_pressure mylog.info('Using polytropic EoS for thermal pressure.') elif ('amrvac', 'b1') in self.field_list: pressure_recipe = _full_thermal_pressure_MHD mylog.info('Using full MHD energy for thermal pressure.') else: pressure_recipe = _full_thermal_pressure_HD mylog.info('Using full HD energy for thermal pressure.') elif self.ds._c_adiab is not None: pressure_recipe = _adiabatic_thermal_pressure mylog.info( 'Using adiabatic EoS for thermal pressure (isothermal).') mylog.warning( 'If you used usr_set_pthermal you should redefine the thermal_pressure field.' ) if pressure_recipe is not None: self.add_field( ('gas', 'thermal_pressure'), function=pressure_recipe, units=us['density'] * us['velocity']**2, dimensions=dimensions.density * dimensions.velocity**2, sampling_type='cell') # sound speed and temperature depend on thermal pressure def _sound_speed(field, data): return np.sqrt(data.ds.gamma * data["gas", "thermal_pressure"] / data["gas", "density"]) self.add_field(("gas", "sound_speed"), function=_sound_speed, units=us["velocity"], dimensions=dimensions.velocity, sampling_type="cell") else: mylog.warning( "e not found and no parfile passed, can not set thermal_pressure." )