]> https://gitweb.dealii.org/ - dealii.git/commitdiff
Document the intention of the numbers::invalid_* variables. 16083/head
authorWolfgang Bangerth <bangerth@colostate.edu>
Tue, 3 Oct 2023 03:18:13 +0000 (21:18 -0600)
committerWolfgang Bangerth <bangerth@colostate.edu>
Thu, 5 Oct 2023 14:49:09 +0000 (08:49 -0600)
doc/doxygen/headers/glossary.h
include/deal.II/base/index_set.h
include/deal.II/base/types.h

index 38055023afcb77489cc54b9801691f0521f8ed96..10d7a916724a57d9c089b577958c97762c52cb85 100644 (file)
  * </dd>
  *
  *
+ * <dt class="glossary">@anchor GlossInvalidValue <b>Invalid value</b></dt>
+ * <dd>
+ * A common problem in software design is what to do if a function needs to
+ * return something like "this value does not exist". An example of this
+ * could be the function `IndexSet::index_within_set(i)` that returns the
+ * how many'th element of the set `i` is. Clearly, the return value of this
+ * function should be an unsigned integer type, the result always being
+ * a count (zero or positive). The question is what to do if the index
+ * `i` is not actually in the set. One *could* consider this a bug: You
+ * can't ask an index set for the position of an index that is not in the
+ * set, and so an exception should be thrown: The user should first check
+ * with `IndexSet::is_element(i)` whether `i` is an element of the set,
+ * and only then should they call `IndexSet::index_within_set(i)`.
+ * But sometimes there are situations where one simply wants to
+ * return a regular count if `i` is in the set, and some kind of
+ * "exceptional" value if it is not.
+ *
+ * Similar questions appear when one writes code of the following kind:
+ * @code
+ *   unsigned int value;
+ *   if (some condition)
+ *     value = 13;
+ *   [...] // much code
+ *   if (some other condition)
+ *     value = 42;
+ *
+ *   launch_the_rocket(value); // something important and expensive
+ * @endcode
+ * Here, the programmer may know that either `some condition` or
+ * `some other condition` is true, and that consequently `value` is
+ * always initialized at the end of the block. But there are good
+ * reasons not to trust this. First, programmers make mistakes, and
+ * so it is conceivable that there are situations where the variable
+ * ends up uninitialized, even though that wasn't intended. Second,
+ * code changes over time and while the "either `some condition` or
+ * `some other condition` is true" situation may hold at the time
+ * of development of the software, when code is moved around
+ * or undergoes bug fixes and functionality enhancement, things may
+ * change and the variable may go uninitialized. A better way
+ * to write this code would be like this:
+ * @code
+ *   unsigned int value = some_invalid_value;
+ *   if (some condition)
+ *     value = 13;
+ *   [...] // much code
+ *   if (some other condition)
+ *     value = 42;
+ *
+ *   Assert (value != some_invalid_value, "some error message");
+ *   launch_the_rocket(value); // something important and expensive
+ * @endcode
+ * As before, the issue is what `some_invalid_value` should be.
+ *
+ * This is such a common problem that many, mostly ad-hoc, solutions
+ * are widely used. In some cases, parts of the values of a type can
+ * be used. For example, `sqrt` must necessarily return a
+ * non-negative value because, well, all square roots are non-negative.
+ * As a consequence, this function could return error codes as negative
+ * values. (In truth, though,
+ * [`sqrt`](https://en.cppreference.com/w/cpp/numeric/math/sqrt)
+ * returns `NaN` if one provides
+ * a negative input -- here, `NaN` stands for "not a number" and is,
+ * just like negative numbers, a stand-in for a value that can not
+ * happen as part of the regular operations of this function and can
+ * consequently be used to indicate errors.) Similarly, functions such as
+ * [`printf()`](https://en.cppreference.com/w/c/io/fprintf)
+ * either return the number of characters printed or, in case
+ * of an error in the inputs, a negative value. Finally, the
+ * [`fopen`](https://en.cppreference.com/w/cpp/io/c/fopen)
+ * function returns a pointer to a file descriptor (similar to a
+ * `std::iofstream` object) but if the file cannot be opened,
+ * for example because the file does not exist or the directory in which
+ * it supposedly is does not exist, then the function returns a `nullptr`.
+ *
+ * All of these examples use that the returned object is of a type whose
+ * set of possible values contains values that cannot be legitimate
+ * return values (in mathematical language: they are not part of the
+ * "range" of the function) and that can consequently be used to indicate
+ * errors. This is awkward because the mapping of error codes into the
+ * space of possible return values depends very much on the function and
+ * what it can and cannot return. For example, `sqrt()` could return `-1`
+ * as an error, but `sin()` can not because `-1` is a valid return value.
+ * Also, not all functions allow for this. For example, the
+ * [`strtol`](https://en.cppreference.com/w/c/string/byte/strtol)
+ * ("string to long (integer)") function takes a string as input and
+ * returns a long integer as output. But since clearly every possible
+ * value of the type `long int` can legitimately be returned, errors in
+ * the input (say, if someone provided the string `"nonsense"` as
+ * input) cannot be indicated via the return object, and the function
+ * needs to indicate errors through another mechanism. The C language
+ * does that by letting functions such as `strtol` set the global variable
+ * `errno` to a nonzero value to indicate an error (an approach that
+ * comes with its own set of problems, among which are that people
+ * tend to forget the value of this variable after calling the function).
+ *
+ * The examples listed above date back to the time when C was first
+ * developed, in the late 1960s and 1970s. C++ solves this conundrum
+ * in a more systematic way. First, functions can throw exceptions of
+ * any type, and one can think of a thrown exception as simply another
+ * possible return value of functions that indicates errors without
+ * requiring having a part of the value space of the return type of a
+ * function occupied for error codes. In fact, the type of an exception
+ * is completely decoupled from the usual return type: You can pass as
+ * much information through exceptions you throw, even if the function
+ * in question returns just a meager `int` in regular operation. This
+ * approach is used in a number of deal.II functions: If inputs don't
+ * make sense, the program is either aborted (typically via an
+ * `Assert` statement) if the inputs are believed to be hard-coded --
+ * say, when adding vectors of different length -- or an exception is
+ * thrown via C++'s `throw` statement. The function
+ * Mapping::transform_real_to_unit_cell() is an example of the latter.
+ * Second, in newer C++ standards, one can use the
+ * [`std::expected<T,E>`](https://en.cppreference.com/w/cpp/utility/expected)
+ * class as the return value that can be thought as "this function
+ * returns objects of type `T`, but if an error was detected, then the
+ * function instead returns an object of type `E`". You can then ask
+ * the returned object whether it stores one or the other. In cases of
+ * errors, one would typically store an explanation of the error in `E`,
+ * in much the same way as the function could throw an exception of type
+ * `E`. For example, a perhaps better design for the the `fopen` function
+ * mentioned above could return `std::expected<FILE,std::string>` where
+ * if successful, it returns a `FILE` object that identifies the file
+ * for writing and reading; if it fails, the function would store a textual
+ * description of what went wrong in the second slot of the `std::expected`
+ * object (or perhaps an element of an `enum` that simply provides an
+ * enumeration of possible reasons for failure). Relatedly, if it is not
+ * necessary to provide a reason for the failure, functions could simply
+ * return an object of type
+ * [`std::optional<T>`](https://en.cppreference.com/w/cpp/utility/optional)
+ * that may or may not hold an object of type `T`, and that one can ask
+ * about that. This would be the right approach for the
+ * `IndexSet::index_within_set(i)` function mentioned above: If `i` is
+ * an element of the set, then it returns an object of type
+ * `std::optional<IndexSet::size_type>` that contains the requested value;
+ * if `i` was not in the set, then it returns an empty
+ * `std::optional<IndexSet::size_type>` object.
+ *
+ * A third approach, widely used in deal.II, is to *explicitly* declare
+ * part of the range space as "exceptional". For example, many functions
+ * in deal.II deal with indices of degrees of freedom. These are encoded
+ * as unsigned integers, except that we explicitly declare the value
+ * 4294967295 as an invalid value that indicates an error. (This specific
+ * value happens to be the largest unsigned integer; computations are
+ * unlikely to be so large that they use this specific value in a legitimate
+ * sense.) Many of the data types used in deal.II, such as
+ * types::global_dof_index, types::active_fe_index, types::material_id
+ * explicitly consider one possible value representable by these
+ * types as "invalid" and use it to report errors of uninitialized
+ * variables. These values typically have names such as
+ * numbers::invalid_unsigned_int, numbers::invalid_material_id, etc.
+ *
+ * (As a postscript, the `strtol` function mentioned above uses this sort of
+ * approach as well. If the input to that function is invalid, it not only
+ * sets the global variable `errno`, but *also* returns either `LONG_MAX`
+ * or `LONG_MIN`. These are the largest and smallest long integer values.
+ * In other words, the function's definition *explicitly* marks these values
+ * as "invalid" or "exceptional", even though one could legitimately expect
+ * to provide the function with a string for which the conversion to a long
+ * integer would result in these values. This is at its core the same
+ * approach we use in deal.II with the invalid values mentioned above,
+ * except that deal.II uses variable names that reflect the underlying
+ * use case (such as whether a value reflects an invalid value
+ * for DoF indices or manifold ids), rather than just the type: When
+ * using numbers::invalid_material_id, you don't need to know what
+ * type is actually used to represent material ids.)
+ * </dd>
+ *
+ *
  * <dt class="glossary">@anchor GlossLagrange <b>Lagrange elements</b></dt>
  * <dd>Finite elements based on Lagrangian interpolation at
- * @ref GlossSupport "support points".
+ * @ref GlossSupport "support points"
+ * are called "Lagrange elements". Their node functionals correspond
+ * to evaluation of shape functions at these support points.
  * </dd>
  *
  *
index 5c74bb8e8f57ea4360c6662e4e3cc1687e4fc1b8..b239476a6770f633d6e3d645eaf5973b1906e0f3 100644 (file)
@@ -279,7 +279,7 @@ public:
    * Return the how-manyth element of this set (counted in ascending order) @p
    * global_index is. @p global_index needs to be less than the size(). This
    * function returns numbers::invalid_dof_index if the index @p global_index is not actually
-   * a member of this index set, i.e. if is_element(global_index) is false.
+   * a member of this index set, i.e. if `is_element(global_index)` is false.
    */
   size_type
   index_within_set(const size_type global_index) const;
index c318ea4c46a5428e9d567f899c032a2632c96658..fff3fb45d7bf07b1f1c7873f424158c66ff3532d 100644 (file)
@@ -209,6 +209,10 @@ namespace numbers
    * integer. This value is widely used throughout the library as a marker for
    * an invalid unsigned integer value, such as an invalid array index, an
    * invalid array size, and the like.
+   *
+   * This value is an example of an
+   * @ref GlossInvalidValue "invalid value".
+   * See there for more information.
    */
   static const unsigned int invalid_unsigned_int =
     static_cast<unsigned int>(-1);
@@ -218,17 +222,29 @@ namespace numbers
    * This value is used throughout the library as a marker for an invalid
    * size_type value, such as an invalid array index, an invalid array size,
    * and the like. Invalid_size_type is equivalent to invalid_dof_index.
+   *
+   * This value is an example of an
+   * @ref GlossInvalidValue "invalid value".
+   * See there for more information.
    */
   const types::global_dof_index invalid_size_type =
     static_cast<types::global_dof_index>(-1);
 
   /**
    * An invalid value for active and future fe indices.
+   *
+   * This value is an example of an
+   * @ref GlossInvalidValue "invalid value".
+   * See there for more information.
    */
   const types::fe_index invalid_fe_index = static_cast<types::fe_index>(-1);
 
   /**
    * An invalid value for indices of degrees of freedom.
+   *
+   * This value is an example of an
+   * @ref GlossInvalidValue "invalid value".
+   * See there for more information.
    */
   const types::global_dof_index invalid_dof_index =
     static_cast<types::global_dof_index>(-1);
@@ -238,22 +254,34 @@ namespace numbers
    * entry on
    * @ref GlossCoarseCellId "coarse cell IDs"
    * for more information.
+   *
+   * This value is an example of an
+   * @ref GlossInvalidValue "invalid value".
+   * See there for more information.
    */
   const types::coarse_cell_id invalid_coarse_cell_id =
     static_cast<types::coarse_cell_id>(-1);
 
   /**
    * Invalid material_id which we need in several places as a default value.
-   * We assume that all material_ids lie in the range [0,
-   * invalid_material_id).
+   * We assume that all material_ids lie in the range `[0,
+   * invalid_material_id)`.
+   *
+   * This value is an example of an
+   * @ref GlossInvalidValue "invalid value".
+   * See there for more information.
    */
   const types::material_id invalid_material_id =
     static_cast<types::material_id>(-1);
 
   /**
    * Invalid boundary_id which we need in several places as a default value.
-   * We assume that all valid boundary_ids lie in the range [0,
-   * invalid_boundary_id).
+   * We assume that all valid boundary_ids lie in the range `[0,
+   * invalid_boundary_id)`.
+   *
+   * This value is an example of an
+   * @ref GlossInvalidValue "invalid value".
+   * See there for more information.
    *
    * @see
    * @ref GlossBoundaryIndicator "Glossary entry on boundary indicators"
@@ -263,14 +291,18 @@ namespace numbers
 
   /**
    * A boundary indicator number that we reserve for internal faces.  We
-   * assume that all valid boundary_ids lie in the range [0,
-   * internal_face_boundary_id).
+   * assume that all valid boundary_ids lie in the range `[0,
+   * internal_face_boundary_id)`.
    *
    * This is an indicator that is used internally (by the library) to
    * differentiate between faces that lie at the boundary of the domain and
    * faces that lie in the interior of the domain. You should never try to
    * assign this boundary indicator to anything in user code.
    *
+   * This value is an example of an
+   * @ref GlossInvalidValue "invalid value".
+   * See there for more information.
+   *
    * @see
    * @ref GlossBoundaryIndicator "Glossary entry on boundary indicators"
    */
@@ -280,6 +312,10 @@ namespace numbers
   /**
    * A manifold_id we reserve for the default flat Cartesian manifold.
    *
+   * This value is an example of an
+   * @ref GlossInvalidValue "invalid value".
+   * See there for more information.
+   *
    * @see
    * @ref GlossManifoldIndicator "Glossary entry on manifold indicators"
    */
@@ -291,6 +327,10 @@ namespace numbers
    * valid id but is used, for example, for default arguments to indicate a
    * subdomain id that is not to be used.
    *
+   * This value is an example of an
+   * @ref GlossInvalidValue "invalid value".
+   * See there for more information.
+   *
    * See the
    * @ref GlossSubdomainId "glossary"
    * for more information.
@@ -311,6 +351,10 @@ namespace numbers
    * as well as the
    * @ref distributed
    * module for more information.
+   *
+   * This value is an example of an
+   * @ref GlossInvalidValue "invalid value".
+   * See there for more information.
    */
   const types::subdomain_id artificial_subdomain_id =
     static_cast<types::subdomain_id>(-2);

In the beginning the Universe was created. This has made a lot of people very angry and has been widely regarded as a bad move.

Douglas Adams


Typeset in Trocchi and Trocchi Bold Sans Serif.