Skip to content

Commit

Permalink
Merge pull request #130 from mabruzzo/new_dynamic_api
Browse files Browse the repository at this point in the history
Take 2 of Dynamic api PR
  • Loading branch information
brittonsmith committed Feb 6, 2023
2 parents 54c29f9 + a1ebc29 commit 4158605
Show file tree
Hide file tree
Showing 14 changed files with 767 additions and 620 deletions.
18 changes: 18 additions & 0 deletions doc/source/Contributing.rst
Original file line number Diff line number Diff line change
@@ -1 +1,19 @@
.. include:: ../../CONTRIBUTING.rst

.. _adding-new-params:

Adding a New Parameter
----------------------

This section provides a short list of tasks to complete when a new field is introduced to the :c:data:`chemistry_data` struct:

1. Add a new entry to ``src/clib/grackle_chemistry_data_fields.def`` for this field. This file holds a central list of fields in :c:data:`chemistry_data`. Doing this accomplishes the following three things:

(i) the field is initialized to the appropriate default value.
(ii) the new field and its value are printed when ``grackle_verbose = 1``
(iii) the field can be accessed by the functions providing the dynamic access to members of :c:data:`chemistry_data` (see :ref:`dynamic-api`)
(iv) the new field is accessible from Pygrackle (because that interface uses the functions providing dynamic access)

2. Update the fortran definition for the ``grackle_chemistry_data`` type (in ``src/clib/grackle_fortran_interface.def``). This type must exactly match the definition of :c:data:`chemistry_data` and is used to integrate Grackle into simulation codes written in Fortran.

3. Add docummentation in ``doc/source/Parameters.rst`` for your new parameter.
60 changes: 60 additions & 0 deletions doc/source/Integration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ will return an integer indicating success (1) or failure (0).

The Grackle is now ready to be used.

As an aside, see :ref:`dynamic-api` for a description of an alternative approach for configuring a :c:type:`chemistry_data` struct. This other approach may provide additional compatability with multiple versions of Grackle, and in some cases may facillitate less-verbose, easier-to-maintain code.

.. _openmp:

Running with OpenMP
Expand Down Expand Up @@ -760,3 +762,61 @@ information:
printf ("The Grackle Version: %s\n", gversion.version);
printf ("Git Branch: %s\n", gversion.branch);
printf ("Git Revision: %s\n", gversion.revision);

.. _dynamic-api:

Dynamic configuration of Chemistry Data
---------------------------------------

The functions providing dynamic access to the fields of :c:data:`chemistry_data` are useful for maintaining backwards compatibility with older versions of Grackle (that also provide this API) as new fields get added to :c:data:`chemistry_data`. This is exemplified in the following scenario.

Suppose Grackle is updated to have a new heating/cooling mechanism, and to allow users to control that mechanism two new fields are added to :c:data:`chemistry_data`:
* an ``int`` field called ``use_fancy_feature``
* a ``double`` field called ``fancy_feature_param``

Now suppose a downstream simulation code, written in ``c`` or ``c++``, wanted to support configuration of this feature. In this scenario, imagine that we have a pointer to a :c:data:`chemistry_data` structure called ``my_grackle_data``.

The obvious way to configure this feature is to include the following snippet in the simulation code:

.. code-block:: c++

if (configure_fancy_feature) {
my_grackle_data->use_fancy_feature = 1;
my_grackle_data->fancy_feature_param = 5.0; // arbitrary value
}

However, inclusion of the above snippet will prevent the simulation code from compiling if the user has a version of Grackle installed in which :c:data:`chemistry_data` does not have the ``use_fancy_feature`` and ``fancy_feature_param`` fields. Consequently, such users will have to update Grackle.

* This can be inconvenient when a user has no interest in using this new feature, but needs an unrelated feature/bugfix introduced to the code in a subsequent changeset

* This is especially inconvenient if a user is prototying a new feature in a custom Grackle branch in which the :c:data:`chemistry_data` struct is missing these fields.

The following snippet shows how the dynamic access API can be used in the same way for versions of Grackle that include these parameters, and don't set the features in cases

.. code-block:: c++

if (configure_fancy_feature) {
int* use_fancy_feature = local_chemistry_data_access_int(
my_grackle_data, "use_fancy_feature"
);
double* fancy_feature_param = local_chemistry_data_access_double(
my_grackle_data, "fancy_feature_param"
);

if ((use_fancy_feature == NULL) || (fancy_feature_param == NULL)){
fprintf(stderr, "Update grackle version to use fancy feature\n");
} else {
*use_fancy_feature = 1;
*fancy_feature_param = 5.0;
}
}
There are a few points worth noting:

* As the above snippets show, the dynamic api clearly produces more verbose code when configuring :c:data:`chemistry_data` field-by-field. However, in codes where users configure Grackle by specifying the name of fields in the :c:data:`chemistry_data` struct and the associated values in a parameter file, the dynamic API can facillitate MUCH less verbose code. Under certain implementations, it may not even be necessary to modify a simulation code to support newly-introduced grackle parameters.

* The dynamic API is slower than configuring :c:data:`chemistry_data` in the classic approach. However, this shouldn't be an issue since :c:data:`chemistry_data` is usually just configured once when the simulation code starts up.

* The highlighted functions can also be used in tandem with other functions described in :ref:`dynamic_api_functions` to simplify (de)serialization of :c:data:`chemistry_data`.

* For completeness, the dynamic API also provides an analogous function for configuring string parameters.
2 changes: 2 additions & 0 deletions doc/source/Parameters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Parameters and Data Files
Parameters
----------

See :ref:`adding-new-params` for guidelines for adding new parameters.

For all on/off integer flags, 0 is off and 1 is on.

.. c:var:: int use_grackle
Expand Down
63 changes: 63 additions & 0 deletions doc/source/Reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,69 @@ initialization functions discussed in :ref:`internal_functions`.
:rtype: int
:returns: 1 (success) or 0 (failure)
.. _dynamic_api_functions:
Dynamic Configuration Functions
+++++++++++++++++++++++++++++++
The following functions are used to provide dynamic access to members of the :c:data:`chemistry_data` struct. They will return ``NULL`` when ``my_chemistry`` is ``NULL``, ``param_name`` isn't a known parameter, or the ``param_name`` is not associated with the type mentioned in the function name.
.. c:function:: int* local_chemistry_data_access_int(chemistry_data *my_chemistry, const char* param_name);
Returns the pointer to the member of ``my_chemistry`` associated with ``param_name``.

:param chemistry_data* my_chemistry: the structure returned by :c:func:`_set_default_chemistry_parameters`
:param const char* param_name: the name of the parameter to access.
:rtype: int*

.. c:function:: double* local_chemistry_data_access_double(chemistry_data *my_chemistry, const char* param_name);
Returns the pointer to the member of ``my_chemistry`` associated with ``param_name``.

:param chemistry_data* my_chemistry: the structure returned by :c:func:`_set_default_chemistry_parameters`
:param const char* param_name: the name of the parameter to access.
:rtype: double*

.. c:function:: char** local_chemistry_data_access_string(chemistry_data *my_chemistry, const char* param_name);
Returns the pointer to the member of ``my_chemistry`` associated with ``param_name``.

:param chemistry_data* my_chemistry: the structure returned by :c:func:`_set_default_chemistry_parameters`
:param const char* param_name: the name of the parameter to access.
:rtype: char**

The following functions are used to query the name of the ith field of the :c:data:`chemistry_data` struct of a particular type.

.. c:function:: const char* param_name_int(size_t i);
Query the name of the ith ``int`` field from :c:data:`chemistry_data`.

.. warning:: The order of parameters may change between different versions of Grackle.

:param size_t i: The index of the accessed parameter
:rtype: const char*
:returns: Pointer to the string-literal specifying the name. This is ``NULL``, if :c:data:`chemistry_data` has ``i`` or fewer ``int`` members

.. c:function:: const char* param_name_double(size_t i);
Query the name of the ith ``double`` field from :c:data:`chemistry_data`.

.. warning:: The order of parameters may change between different versions of Grackle.

:param size_t i: The index of the accessed parameter
:rtype: const char*
:returns: Pointer to the string-literal specifying the name. This is ``NULL``, if :c:data:`chemistry_data` has ``i`` or fewer ``double`` members.

.. c:function:: const char* param_name_string(size_t i);
Query the name of the ith ``string`` field from :c:data:`chemistry_data`.

.. warning:: The order of parameters may change between different versions of Grackle.

:param size_t i: The index of the accessed parameter
:rtype: const char*
:returns: Pointer to the string-literal specifying the name. This is ``NULL``, if :c:data:`chemistry_data` has ``i`` or fewer ``string`` members.

.. _internal_functions:

Internal Functions
Expand Down
1 change: 1 addition & 0 deletions src/clib/Make.config.objects
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ OBJS_CONFIG_LIB = \
cool1d_cloudy_old_tables_g.lo \
cool1d_multi_g.lo \
cool_multi_time_g.lo \
dynamic_api.lo \
grackle_units.lo \
index_helper.lo \
initialize_chemistry_data.lo \
Expand Down
116 changes: 116 additions & 0 deletions src/clib/dynamic_api.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/***********************************************************************
/
/ Defines functions that provide access to fields of the chemistry_data
/ struct by dynamically specifying strings. If external applications use
/ these functions to access/set fields, rather than directly accessing
/ the fields of the struct, the applications will be to maintain
/ compatability with multiple versions grackle (as more fields get added
/ to chemistry_data)
/
/
/ Copyright (c) 2013, Enzo/Grackle Development Team.
/
/ Distributed under the terms of the Enzo Public Licence.
/
/ The full license is in the file LICENSE, distributed with this
/ software.
************************************************************************/

#include <stddef.h>
#include <string.h>
#include "grackle_chemistry_data.h"

// The following enum is only used internally
enum param_type {INT_PARAM, DOUBLE_PARAM, STRING_PARAM};

// initialize _param_list, which holds an entry for each field in
// chemistry_data. This is only used internally

typedef struct {
const enum param_type type;
const size_t offset;
const char * name;
} param_entry;

static const param_entry _param_list[] =
{
#define ENTRY(FIELD, TYPE, DEFAULT_VAL) \
{ TYPE ## _PARAM, offsetof(chemistry_data, FIELD), #FIELD },
#include "grackle_chemistry_data_fields.def"
#undef ENTRY
};

// This is only used internally
static const size_t _n_params = sizeof(_param_list) / sizeof(param_entry);


// define functions for accessing field values
// - local_chemistry_data_access_double
// - local_chemistry_data_access_int
//
// The current implementation has O(N) complexity where N is the number of
// fields of chemistry_data. Since configuration just happens once, this
// probably doesn't need to be very fast...
//
// A faster implementation that does a lot less work at runtime is definitely
// possible (it would just involve more code).


// retrieves a pointer to the field of my_chemistry that's named ``name``.
//
// This returns a NULL pointer if: my_chemistry is NULL, the field doesn't
// exist, or the field doesn't have the specified type
static void* _get_field_ptr(chemistry_data* my_chemistry, const char* name,
enum param_type type)
{
if (my_chemistry == NULL) { return NULL; }

for (size_t param_index = 0; param_index < _n_params; param_index++){
const param_entry* entry = _param_list + param_index;
if ((strcmp(entry->name, name) == 0) & (entry->type == type)){
return (void*)( (char*)my_chemistry + entry->offset );
}
}
return NULL;
}

double* local_chemistry_data_access_double(chemistry_data* my_chemistry,
const char* param_name)
{ return (double*)_get_field_ptr(my_chemistry, param_name, DOUBLE_PARAM); }

int* local_chemistry_data_access_int(chemistry_data* my_chemistry,
const char* param_name)
{ return (int*)_get_field_ptr(my_chemistry, param_name, INT_PARAM); }

char** local_chemistry_data_access_string(chemistry_data* my_chemistry,
const char* param_name)
{ return (char**)_get_field_ptr(my_chemistry, param_name, STRING_PARAM); }

// define functions for accessing the names of chemistry_data
// - param_name_double
// - param_name_int
//
// These are primarily needed for testing purposes and can be used for
// serialization. The current implementation is slow. A faster alternative
// would define separate lists for int parameters and double parameters

// returns the name of the ``i``th parameter of the specified type. This returns
// NULL when there are ``i`` or fewer parameters of the specified type
static const char* _param_name(size_t i, enum param_type type)
{
size_t type_count = 0; // # of parameters of type that have been encountered
for (size_t param_index = 0; param_index < _n_params; param_index++){
if (_param_list[param_index].type != type){
continue;
} else if (type_count == i){
return _param_list[param_index].name;
} else {
type_count++;
}
}
return NULL;
}

const char* param_name_double(size_t i){ return _param_name(i, DOUBLE_PARAM); }
const char* param_name_int(size_t i){ return _param_name(i, INT_PARAM); }
const char* param_name_string(size_t i){ return _param_name(i, STRING_PARAM); }
11 changes: 11 additions & 0 deletions src/clib/grackle.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ int _initialize_chemistry_data(chemistry_data *my_chemistry,
chemistry_data_storage *my_rates,
code_units *my_units);

int* local_chemistry_data_access_int(chemistry_data* my_chemistry,
const char* param_name);
double* local_chemistry_data_access_double(chemistry_data* my_chemistry,
const char* param_name);
char** local_chemistry_data_access_string(chemistry_data* my_chemistry,
const char* param_name);

const char* param_name_int(size_t i);
const char* param_name_double(size_t i);
const char* param_name_string(size_t i);

int solve_chemistry(code_units *my_units,
grackle_field_data *my_fields,
double dt_value);
Expand Down

0 comments on commit 4158605

Please sign in to comment.