diff --git a/logger/examples/reader_example.py b/logger/examples/reader_example.py index f75d438347f277e8cb86c90501b55b8a75538309..6fe65399cc70de43b6638820c19f41486ee2d9df 100644 --- a/logger/examples/reader_example.py +++ b/logger/examples/reader_example.py @@ -47,9 +47,17 @@ print("basename: %s" % basename) print("time: %g" % time) # read the logger +gas_type = 0 with logger.Reader(basename, verbose=0) as reader: t = reader.get_time_limits() - pos, ent = reader.get_particle_data(["Coordinates", "Entropies"], time) + + fields = reader.get_list_fields(gas_type) + if ("Coordinates" not in fields or + "Entropies" not in fields): + raise Exception("Field not found in the logfile") + + pos, ent = reader.get_particle_data( + ["Coordinates", "Entropies"], time, gas_type) print("Min/Max of the position:", pos.min(), pos.max()) print("Min/Max of the entropy:", ent.min(), ent.max()) diff --git a/logger/logger_python_wrapper.c b/logger/logger_python_wrapper.c index 7278a5f357e213a43d220f06022a85970e9c22a4..9c7336dacd09e868cab2f879a398f38e2ec6a4bd 100644 --- a/logger/logger_python_wrapper.c +++ b/logger/logger_python_wrapper.c @@ -272,6 +272,138 @@ static PyObject *pyExit(__attribute__((unused)) PyObject *self, Py_RETURN_NONE; } + +static PyObject *pyGetListFields(__attribute__((unused)) PyObject *self, + PyObject *args) { + PyObjectReader *self_reader = (PyObjectReader *)self; + if (!self_reader->ready) { + error_python( + "The logger is not ready yet." + "Did you forget to open it with \"with\"?"); + } + + /* input variables. */ + PyObject *types = Py_None; + + /* parse the arguments. */ + if (!PyArg_ParseTuple(args, "|O", &types)) return NULL; + + /* Get the type of particles to read. */ + int read_types[swift_type_count] = {0}; + /* By default, we read everything */ + if (types == Py_None) { + for (int i = 0; i < swift_type_count; i++) { + read_types[i] = 1; + } + } + /* Deal with the case of a single int. */ + else if (PyLong_Check(types)) { + const size_t type = PyLong_AsSize_t(types); + if (type >= swift_type_count) { + error_python("Unexpected particle type %zi", type); + } + read_types[type] = 1; + } + /* Deal with the case of a list */ + else if (PyList_Check(types)) { + const size_t size = PyList_Size(types); + for (size_t i = 0; i < size; i++) { + PyObject *cur = PyList_GetItem(types, i); + const size_t type = PyLong_AsSize_t(cur); + if (type >= swift_type_count) { + error_python("Unexpected particle type %zi", type); + } + read_types[type] = 1; + } + } + + /* initialize the reader. */ + struct logger_reader *reader = &self_reader->reader; + const struct header *h = &reader->log.header; + + /* Create the array to check if a field is present. */ + int *field_present = (int *)malloc(h->masks_count * sizeof(int)); + if (field_present == NULL) { + error("Failed to allocate the memory for the fields present."); + } + + /* Initialize the array */ + for (int i = 0; i < h->masks_count; i++) { + field_present[i] = 1; + } + + /* Check all the fields */ + for (int i = 0; i < swift_type_count; i++) { + /* Skip the types that are not required */ + if (read_types[i] == 0) continue; + + /* Get the list of fields for each particle types. */ + const char **field_names = NULL; + int number_fields = -1; + switch (i) { + case swift_type_gas: + number_fields = hydro_logger_field_count; + field_names = hydro_logger_field_names; + break; + + case swift_type_dark_matter: + case swift_type_dark_matter_background: + number_fields = gravity_logger_field_count; + field_names = gravity_logger_field_names; + break; + + case swift_type_stars: + number_fields = stars_logger_field_count; + field_names = stars_logger_field_names; + break; + + default: + message("Particle type %i not implemented, skipping it", i); + continue; + } + + for (int j = 0; j < h->masks_count; j++) { + /* Skip the fields not found in previous type. */ + if (field_present[j] == 0) continue; + + /* Check if the field is present */ + int found = 0; + for (int k = 0; k < number_fields; k++) { + if (strcmp(h->masks[j].name, field_names[k]) == 0) { + found = 1; + break; + } + } + + /* Set the field as not found */ + if (!found) field_present[j] = 0; + } + } + + /* Count the number of fields found */ + int number_fields = 0; + for (int i = 0; i < h->masks_count; i++) { + number_fields += field_present[i]; + } + + /* Create the python list for the output*/ + PyObject *list = PyList_New(number_fields); + int current = 0; + for (int i = 0; i < h->masks_count; i++) { + /* Keep only the field present. */ + if (field_present[i] == 0) continue; + + PyObject *name = PyUnicode_FromString(h->masks[i].name); + PyList_SetItem(list, current, name); + current += 1; + } + + /* Free the memory. */ + free(field_present); + + return list; +} + /** * @brief Read some fields at a given time. * @@ -391,9 +523,20 @@ static PyMethodDef libloggerReaderMethods[] = { " The list of fields (e.g. 'Coordinates', 'Entropies', ...)\n\n" "time: float\n" " The time at which the fields must be read.\n\n" + "Returns\n" "-------\n\n" "list_of_fields: list\n" " Each element is a numpy array containing the corresponding field.\n"}, + {"get_list_fields", pyGetListFields, METH_VARARGS, + "Read the list of available fields in the logfile.\n\n" + "Parameters\n" + "----------\n\n" + "type: int, list\n" + " The particle type for the list of fields\n\n" + "Returns\n" + "-------\n" + "fields: tuple\n" + " The list of fields present in the logfile.\n"}, {"__enter__", pyEnter, METH_VARARGS, ""}, {"__exit__", pyExit, METH_VARARGS, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ @@ -438,6 +581,10 @@ static PyTypeObject PyObjectReader_Type = { ">>> import liblogger as logger\n" ">>> with logger.Reader(\"index_0000\") as reader:\n" ">>> t0, t1 = reader.get_time_limits()\n" + ">>> fields = reader.get_list_fields()\n" + ">>> if \"Coordinates\" not in fields:\n" + ">>> raise Exception(\"Field Coordinates not present in the " + "logfile.\")\n" ">>> pos, ent = reader.get_particle_data([\"Coordinates\", " "\"Entropies\"]" ", 0.5 * (t0 + t1))\n",