From a0d11dce96b1e5729bde5ef11386bf928257bb50 Mon Sep 17 00:00:00 2001 From: lhausamm <loic_hausammann@hotmail.com> Date: Fri, 8 Dec 2017 10:38:51 +0100 Subject: [PATCH] Cooling is working and able to reproduce grackle paper graphs --- pyswiftsim/structure.py | 126 ++++++++++++++++++-------------------- setup.py | 37 +++++++---- src/cooling_wrapper.c | 95 ++++++++++++++++++++++++++--- src/cooling_wrapper.h | 2 +- src/part_wrapper.c | 2 +- src/pyswiftsim_tools.c | 26 ++++++++ src/pyswiftsim_tools.h | 17 ++++++ src/units_wrapper.c | 2 +- src/wrapper.c | 9 ++- test/cooling.yml | 51 ++++++++++++++++ test/test_cooling.py | 132 +++++++++++++++++++++++++++++++++++++--- 11 files changed, 400 insertions(+), 99 deletions(-) create mode 100644 test/cooling.yml diff --git a/pyswiftsim/structure.py b/pyswiftsim/structure.py index c183a04..6197a22 100644 --- a/pyswiftsim/structure.py +++ b/pyswiftsim/structure.py @@ -1,3 +1,5 @@ +from pyswiftsim import wrapper + import struct import numpy from ctypes import * @@ -52,14 +54,14 @@ class SwiftStruct(struct.Struct): Should be implemented for each sub class """ - raise NotImplementedError("SwiftStruct should not be used") + return self.__class__._name @property def struct_format(self): """ String containing the data type of each attribute """ - raise NotImplementedError("SwiftStruct should not be used") + return self.__class__._format @property def struct_substruct(self): @@ -218,13 +220,18 @@ class SwiftStruct(struct.Struct): # # ###################################################################### class ArrayStruct(SwiftStruct): + _name = [ + "array_data" + ] + def __init__(self, struct_format, data, parent, name): super().__init__(struct_format, data, parent) - self._name = name self._format = struct_format + self._name = name def __getitem__(self, ii): data = self.unpack(self.data) + data = self._clean(data) return data[ii] def __setitem__(self, ii, value): @@ -232,20 +239,24 @@ class ArrayStruct(SwiftStruct): data[ii] = value setattr(self.parent, self._name, data) + def _clean(self, data): + if self._format[-1] == "c": + data = b"".join(data) + # find end of txt character + n = data.index(b"\x00") + data = data[:n].decode("utf-8") + + return data + def __str__(self): data = self.unpack(self.data) + data = self._clean(data) return str(data) def getArray(self): return self.unpack(self.data) - @property - def struct_name(self): - return [ - "array_data" - ] - @property def struct_format(self): return self._format @@ -257,24 +268,18 @@ class ArrayStruct(SwiftStruct): # # ###################################################################### class UnitSystem(SwiftStruct): + _format = "ddddd" + _name = [ + "UnitMass_in_cgs", + "UnitLength_in_cgs", + "UnitTime_in_cgs", + "UnitCurrent_in_cgs", + "UnitTemperature_in_cgs", + ] def __init__(self, data, parent=None): super().__init__(self.struct_format, data, parent) - @property - def struct_format(self): - return "ddddd" - - @property - def struct_name(self): - return [ - "UnitMass_in_cgs", - "UnitLength_in_cgs", - "UnitTime_in_cgs", - "UnitCurrent_in_cgs", - "UnitTemperature_in_cgs", - ] - ###################################################################### # # @@ -282,18 +287,8 @@ class UnitSystem(SwiftStruct): # # ###################################################################### class Part(SwiftStruct): - def __init__(self, data, parent=None): - super().__init__(self.struct_format, data, parent) - - @property - def struct_format(self): - print("ERROR, need to fix density/time_bin") - return "qP3d3f3ffffffN7f4c" - - - @property - def struct_name(self): - return [ + _format = "qP3d3f3ffffffN7f4c" + _name = [ "id", "gpart", "pos", @@ -309,6 +304,11 @@ class Part(SwiftStruct): "time_bin" ] + def __init__(self, data, parent=None): + super().__init__(self.struct_format, data, parent) + print("ERROR, need to fix density/time_bin") + + ###################################################################### # # # Parameter # @@ -327,14 +327,6 @@ class Parameter(SwiftStruct): def __init__(self, data, parent=None): super().__init__(self.struct_format, data, parent) - - @property - def struct_format(self): - return Parameter._format - - @property - def struct_name(self): - return Parameter._name ###################################################################### # # @@ -352,15 +344,6 @@ class Section(SwiftStruct): def __init__(self, data, parent=None): super().__init__(self.struct_format, data, parent) - @property - def struct_format(self): - return Section._format - - - @property - def struct_name(self): - return Section._name - ###################################################################### # # # SwiftParams # @@ -384,15 +367,6 @@ class SwiftParams(SwiftStruct): def __init__(self, data, parent=None): super().__init__(self.struct_format, data, parent) - @property - def struct_format(self): - return SwiftParams._format - - - @property - def struct_name(self): - return SwiftParams._name - @property def struct_substruct(self): sec = { @@ -439,11 +413,29 @@ class PhysConst(SwiftStruct): def __init__(self, data, parent=None): super().__init__(self.struct_format, data, parent) - @property - def struct_format(self): - return PhysConst._format +class CoolingFunctionData(SwiftStruct): + cooling_type = wrapper.configGetCooling() + if cooling_type == "const_lambda": + _format = "dffff" + _name = [ + "lambda", + "hydrogen_mass_abundance", + "mean_molecular_weight", + "min_energy", + "cooling_tstep_mult" + ] + elif cooling_type == "grackle": + _format = "200cidd" + _name = [ + "GrackleCloudyTable", + "UVbackground", + "GrackleRedshift", + "GrackleHSShieldingDensityThreshold" + ] + else: + raise ValueError( + "Cooling Type %s not implemented" % cooling_type) - @property - def struct_name(self): - return PhysConst._name + def __init__(self, data, parent=None): + super().__init__(self.struct_format, data, parent) diff --git a/setup.py b/setup.py index 285ff2f..3efb689 100644 --- a/setup.py +++ b/setup.py @@ -13,13 +13,21 @@ import numpy os.environ["CC"] = "mpicc" # deal with arguments -swift_path = None -swift_arg = "--with-swift" -if swift_arg in sys.argv: - i = sys.argv.index(swift_arg) - swift_path = sys.argv[i+1] - sys.argv.remove(swift_arg) - sys.argv.remove(swift_path) +def parseCmdLine(arg, store=False): + ret = False + if arg in sys.argv: + if store: + i = sys.argv.index(arg) + ret = sys.argv[i+1] + sys.argv.remove(ret) + else: + ret = True + + sys.argv.remove(arg) + + return ret + +swift_path = parseCmdLine("--with-swift", store=True) # python lib dependency install_requires=["numpy"] @@ -37,7 +45,7 @@ def getValueFromMakefile(swift_root, value): raise ValueError("Value %s not found in Makefile" % value) -if swift_path is not None: +if swift_path: hdf5_root = getValueFromMakefile(swift_path, "H5CC = ") # link to non pypi dependencies @@ -46,7 +54,7 @@ dependency_links = [] # C includes include = [numpy.get_include()] -if swift_path is not None: +if swift_path: # swift include.extend([ swift_path + "/src", @@ -56,19 +64,22 @@ if swift_path is not None: # hdf5 include.append(hdf5_root + "/include") + # grackle + grackle_inc = getValueFromMakefile(swift_path, "GRACKLE_INCS = -I") + include.append(grackle_inc) + # C libraries lib = ["m", "swiftsim", - "hdf5" + "hdf5", ] lib_dir = [] -if swift_path is not None: +if swift_path: lib_dir.append(swift_path + "/src/.libs") lib_dir.append(hdf5_root + "/lib") - - + # src files c_src = [] diff --git a/src/cooling_wrapper.c b/src/cooling_wrapper.c index ce09541..b81e13f 100644 --- a/src/cooling_wrapper.c +++ b/src/cooling_wrapper.c @@ -3,24 +3,29 @@ #include <cooling.h> #include <cooling_struct.h> +#include <equation_of_state.h> + + +#include <numpy/arrayobject.h> + PyObject* pycooling_init(PyObject* self, PyObject* args) { - PyObject* pyparams; - PyObject* pyus; - PyObject* pypconst; + PyObject *pyparams; + PyObject *pyus; + PyObject *pypconst; if (!PyArg_ParseTuple(args, "OOO", &pyparams, &pyus, &pypconst)) return NULL; - struct swift_params *params = pytools_construct(pyparams, class_swift_params); + struct swift_params *params = (struct swift_params*) pytools_construct(pyparams, class_swift_params); if (params == NULL) return NULL; - struct unit_system *us = pytools_construct(pyus, class_unit_system); + struct unit_system *us = (struct unit_system*) pytools_construct(pyus, class_unit_system); if (us == NULL) return NULL; - struct phys_const *pconst = pytools_construct(pypconst, class_phys_const); + struct phys_const *pconst = (struct phys_const*) pytools_construct(pypconst, class_phys_const); if (pconst == NULL) return NULL; @@ -34,6 +39,80 @@ PyObject* pycooling_init(PyObject* self, PyObject* args) { return pycooling; } -PyObject* pycooling_rate(PyObject* self, PyObject* args) { - pyerror("Not implemented"); +PyArrayObject* pycooling_rate(PyObject* self, PyObject* args) { + IMPORT_ARRAY(); + + PyObject *pycooling; + PyObject *pyus; + PyObject *pypconst; + + PyArrayObject *rho; + PyArrayObject *energy; + + float dt = 1e-3; + + if (!PyArg_ParseTuple(args, + "OOOOO|f", + &pypconst, + &pyus, + &pycooling, + &rho, + &energy, + &dt)) + return NULL; + + if (pytools_check_array(energy, 1, NPY_FLOAT) != SUCCESS) + { + return NULL; + } + + if (pytools_check_array(rho, 1, NPY_FLOAT) != SUCCESS) + { + return NULL; + } + + if (PyArray_DIM(energy, 0) != PyArray_DIM(rho, 0)) + { + pyerror("Density and energy should have the same dimension"); + } + + size_t N = PyArray_DIM(energy, 0); + + struct cooling_function_data *cooling = (struct cooling_function_data*) pytools_construct(pycooling, class_cooling_function_data); + if (cooling == NULL) + return NULL; + + struct unit_system *us = (struct unit_system*) pytools_construct(pyus, class_unit_system); + if (us == NULL) + return NULL; + + struct phys_const *pconst = (struct phys_const*) pytools_construct(pypconst, class_phys_const); + if (pconst == NULL) + return NULL; + + struct part p; + +#ifdef COOLING_GRACKLE + grackle_data.grackle_data_file = cooling->GrackleCloudyTable; +#endif + + PyArrayObject *rate = PyArray_NewLikeArray(energy, NPY_ANYORDER, NULL, 1); + + for(size_t i = 0; i < N; i++) + { + p.rho = *(float*) PyArray_GETPTR1(rho, i); + float u = *(float*) PyArray_GETPTR1(energy, i); + p.entropy = gas_entropy_from_internal_energy(p.rho, u); + + float *tmp = PyArray_GETPTR1(rate, i); + +#ifdef COOLING_GRACKLE + *tmp = cooling_rate(pconst, us, cooling, &p, dt); +#else + *tmp = cooling_rate(pconst, us, cooling, &p); +#endif + } + + return rate; + } diff --git a/src/cooling_wrapper.h b/src/cooling_wrapper.h index 55dcec6..95717e8 100644 --- a/src/cooling_wrapper.h +++ b/src/cooling_wrapper.h @@ -5,7 +5,7 @@ PyObject* pycooling_init(PyObject* self, PyObject* args); -PyObject* pycooling_rate(PyObject* self, PyObject* args); +PyArrayObject* pycooling_rate(PyObject* self, PyObject* args); #endif // __PYSWIFTSIM_COOLING_H__ diff --git a/src/part_wrapper.c b/src/part_wrapper.c index 742d1a7..0fd3220 100644 --- a/src/part_wrapper.c +++ b/src/part_wrapper.c @@ -26,7 +26,6 @@ PyObject* pypart_test_struct(PyObject *self, PyObject *args) p->rho = 12.; p->entropy = 13; p->entropy_dt = 14; - p->last_offset = 15; hydro_init_part(p, NULL); @@ -36,3 +35,4 @@ PyObject* pypart_test_struct(PyObject *self, PyObject *args) return object; } + diff --git a/src/pyswiftsim_tools.c b/src/pyswiftsim_tools.c index a01ede6..3cbad7f 100644 --- a/src/pyswiftsim_tools.c +++ b/src/pyswiftsim_tools.c @@ -8,6 +8,8 @@ #include <cooling_struct.h> #include <Python.h> +#include <numpy/arrayobject.h> + const size_t class_size[class_count] = { sizeof(struct unit_system), @@ -164,3 +166,27 @@ char* pytools_construct(PyObject* obj, int class) Py_DECREF(data); return ret; } + + +int pytools_check_array(PyArrayObject *obj, int dim, int type) +{ + IMPORT_ARRAY(); + + if (!PyArray_Check(obj)) + { + pyerror("Expecting a numpy array"); + } + + if (PyArray_NDIM(obj) != dim) + { + pyerror("Array should be a %i dimensional object", dim); + } + + if (PyArray_TYPE(obj) != type) + { + pyerror("Wrong array type"); + } + + return SUCCESS; + +} diff --git a/src/pyswiftsim_tools.h b/src/pyswiftsim_tools.h index fb2a366..75f2737 100644 --- a/src/pyswiftsim_tools.h +++ b/src/pyswiftsim_tools.h @@ -4,10 +4,20 @@ #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #include <Python.h> +#include <numpy/arrayobject.h> #define DIM 3 #define STRING_SIZE 200 +/* Check if arrays are imported and if not import them */ +#define IMPORT_ARRAY() \ + ({ \ + if (PyArray_API == NULL) \ + { \ + import_array(); \ + } \ + }) + /* Set the error message for python (still need to return NULL) */ #define pyerror(s, ...) \ ({ \ @@ -19,6 +29,12 @@ return FAIL; \ }) +#define pydebug(s, ...) \ + ({ \ + PyErr_Print(); \ + fprintf(stderr, "\n%s:%s():%i: Test " s "\n", __FILE__, \ + __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + }) enum class { class_unit_system, @@ -45,4 +61,5 @@ PyObject* pytools_import(char* module, char* object_name); char* pytools_get_type_name(PyObject *obj); +int pytools_check_array(PyArrayObject *obj, int dim, int type); #endif // __PYSWIFTSIM_TOOLS_H__ diff --git a/src/units_wrapper.c b/src/units_wrapper.c index 47ada02..1858c2b 100644 --- a/src/units_wrapper.c +++ b/src/units_wrapper.c @@ -52,7 +52,7 @@ PyObject* pyunit_system_init(PyObject *self, PyObject *args) PyObject *pypconst = pytools_return(&pconst, class_phys_const); if (pypconst == NULL) - return NULL; + return NULL; return PyTuple_Pack(2, pyus, pypconst); } diff --git a/src/wrapper.c b/src/wrapper.c index 8c4b0ba..7ef79ae 100644 --- a/src/wrapper.c +++ b/src/wrapper.c @@ -3,6 +3,7 @@ #include "parser_wrapper.h" #include "cooling_wrapper.h" #include "pyswiftsim_tools.h" +#include "config_wrapper.h" #include <Python.h> @@ -29,6 +30,12 @@ static PyMethodDef wrapper_methods[] = { {"coolingInit", pycooling_init, METH_VARARGS, "Initialize cooling."}, + {"coolingRate", pycooling_rate, METH_VARARGS, + "Compute the cooling rate."}, + + {"configGetCooling", config_get_cooling, METH_VARARGS, + "Get the cooling type."}, + {NULL, NULL, 0, NULL} /* Sentinel */ }; @@ -47,8 +54,8 @@ PyMODINIT_FUNC PyInit_wrapper(void) { PyObject *m; - Py_Initialize(); import_array(); + Py_Initialize(); m = PyModule_Create(&wrapper_cmodule); if (m == NULL) diff --git a/test/cooling.yml b/test/cooling.yml new file mode 100644 index 0000000..96555e6 --- /dev/null +++ b/test/cooling.yml @@ -0,0 +1,51 @@ +# Define the system of units to use internally. +InternalUnitSystem: + UnitMass_in_cgs: 1.989e43 # Grams + UnitLength_in_cgs: 3.085e21 # Centimeters + UnitVelocity_in_cgs: 20725573.785998672 # Centimeters per second + UnitCurrent_in_cgs: 1 # Amperes + UnitTemp_in_cgs: 1 # Kelvin + +# Parameters governing the time integration +TimeIntegration: + time_begin: 0. # The starting time of the simulation (in internal units). + time_end: 5e-2 # The end time of the simulation (in internal units). + dt_min: 1e-7 # The minimal time-step size of the simulation (in internal units). + dt_max: 1e-4 # The maximal time-step size of the simulation (in internal units). + +# Parameters governing the snapshots +Snapshots: + basename: sedov # Common part of the name of output files + time_first: 0. # Time of the first output (in internal units) + delta_time: 1e-2 # Time difference between consecutive outputs (in internal units) + +# Parameters governing the conserved quantities statistics +Statistics: + delta_time: 1e-3 # Time between statistics output + tasks_per_cell: 1000 + +# Parameters for the hydrodynamics scheme +SPH: + resolution_eta: 1.2348 # Target smoothing length in units of the mean inter-particle separation (1.2348 == 48Ngbs with the cubic spline kernel). + CFL_condition: 0.1 # Courant-Friedrich-Levy condition for time integration. + +# Parameters related to the initial conditions +InitialConditions: + file_name: ./sedov.hdf5 # The file to read + h_scaling: 3.33 + + +# Cooling with Grackle 2.0 +GrackleCooling: + GrackleCloudyTable: CloudyData_UVB=HM2012.h5 # Name of the Cloudy Table + UVbackground: 1 # Enable or not the UV background + GrackleRedshift: 0 # Redshift to use (-1 means time based redshift) + GrackleHSShieldingDensityThreshold: 1.1708e-26 # self shielding threshold in internal system of units + +Gravity: + eta: 0.025 # Constant dimensionless multiplier for time integration. + theta: 0.7 # Opening angle (Multipole acceptance criterion) + epsilon: 0.1 # Softening length (in internal units). + a_smooth: 1.25 # (Optional) Smoothing scale in top-level cell sizes to smooth the long-range forces over (this is the default value). + r_cut_max: 4.5 # (Optional) Cut-off in number of top-level cells beyond which no FMM forces are computed (this is the default value). + r_cut_min: 0.1 # (Optional) Cut-off in number of top-level cells below which no truncation of FMM forces are performed (this is the default value). diff --git a/test/test_cooling.py b/test/test_cooling.py index 616b6af..e2a8c6f 100644 --- a/test/test_cooling.py +++ b/test/test_cooling.py @@ -3,16 +3,134 @@ from pyswiftsim import wrapper from pyswiftsim import structure -filename = "/home/loikki/swift_test/cooling_sedov/sedov.yml" +from copy import deepcopy +import numpy as np +import matplotlib.pyplot as plt +from astropy import units -params = wrapper.parserReadFile(filename) +# +# parameters +# -us, pconst = wrapper.unitSystemInit(params) +# adiabatic index +gamma = 5. / 3. + +# swift params filename +filename = "test/cooling.yml" + +# number of points +N_rho = 3000 +N_T = 3000 + +# density in atom / cm3 +if N_rho == 1: + rho = np.array([1.]) +else: + #rho = np.array([1.]) + rho = np.logspace(-6, 4, N_rho) + +# temperature in K +T = np.logspace(1, 9, N_T) -#print(us) -#print(pconst) +# time step +dt = units.Myr * 1e-5 +dt = dt.to("s") / units.s -print(type(params), type(us), type(pconst)) + + +# generate grid +rho, T = np.meshgrid(rho, T) +shape = rho.shape +rho = deepcopy(rho.reshape(-1)) +T = T.reshape(-1) + +# +# swift init +# + +# parse swift params +params = wrapper.parserReadFile(filename) +# init units +us, pconst = wrapper.unitSystemInit(params) +# init cooling cooling = wrapper.coolingInit(params, us, pconst) -print(cooling) + +# +# Deal with units +# + +# change units of rho and T +# rho +rho *= us.UnitLength_in_cgs**3 * pconst.const_proton_mass +# specific energy +energy = pconst.const_boltzmann_k * T / us.UnitTemperature_in_cgs +energy /= (gamma - 1.) * pconst.const_proton_mass +# time step +dt /= us.UnitTime_in_cgs + +# +# compute rate +# + +# du / dt +rate = wrapper.coolingRate(pconst, us, cooling, + rho.astype(np.float32), + energy.astype(np.float32), + dt) + +# +# plot +# + + +# change units => cgs +energy *= us.UnitLength_in_cgs**2 / us.UnitTime_in_cgs**2 + +rate *= us.UnitLength_in_cgs**2 / us.UnitTime_in_cgs**3 + +if N_rho == 1 or N_T == 1: + plt.figure() + n = rho / (pconst.const_proton_mass * us.UnitLength_in_cgs**3) + proton_mass_in_cgs = pconst.const_proton_mass * us.UnitMass_in_cgs + lambda_ = rate * proton_mass_in_cgs / (n) + + if N_rho == 1: + plt.loglog(T, np.abs(lambda_)) + plt.xlabel("Temperature [K]") + else: + plt.loglog(rho, np.abs(lambda_)) + plt.xlabel("Density [atom / cm3]") + plt.ylabel("Rate") + +else: + cooling_time = energy / rate + cooling_length = np.sqrt(gamma * (gamma-1.) * energy) * cooling_time + + cooling_length = np.log10(np.abs(cooling_length) / units.kpc.to('cm')) + + # reshape + rho = rho.reshape(shape) + T = T.reshape(shape) + energy = energy.reshape(shape) + cooling_length = cooling_length.reshape(shape) + + _min = -7 + _max = 7 + N_levels = 100 + levels = np.linspace(_min, _max, N_levels) + plt.figure() + plt.contourf(rho, T, cooling_length, levels) + plt.xlabel("Density [atom/cm3]") + plt.ylabel("Temperature [K]") + + ax = plt.gca() + ax.set_xscale("log") + ax.set_yscale("log") + + cbar = plt.colorbar() + tc = np.arange(_min, _max, 1.5) + cbar.set_ticks(tc) + cbar.set_ticklabels(tc) + +plt.show() -- GitLab