]> https://gitweb.dealii.org/ - dealii.git/commitdiff
Add function to resolve explicit and cyclic dependencies in sub. maps
authorJean-Paul Pelteret <jppelteret@gmail.com>
Sun, 5 May 2019 15:42:11 +0000 (17:42 +0200)
committerJean-Paul Pelteret <jppelteret@gmail.com>
Fri, 10 May 2019 21:28:27 +0000 (23:28 +0200)
doc/news/changes/minor/20190505Jean-PaulPelteret-1 [new file with mode: 0644]
include/deal.II/differentiation/sd/symengine_scalar_operations.h
source/differentiation/sd/symengine_scalar_operations.cc
tests/symengine/substitution_maps_cyclic_dependencies_01.cc [new file with mode: 0644]
tests/symengine/substitution_maps_cyclic_dependencies_01.output [new file with mode: 0644]
tests/symengine/substitution_maps_cyclic_dependencies_02.cc [new file with mode: 0644]
tests/symengine/substitution_maps_cyclic_dependencies_02.output [new file with mode: 0644]

diff --git a/doc/news/changes/minor/20190505Jean-PaulPelteret-1 b/doc/news/changes/minor/20190505Jean-PaulPelteret-1
new file mode 100644 (file)
index 0000000..740787f
--- /dev/null
@@ -0,0 +1,4 @@
+New: Some utility functions that resolve explicit (and cyclic) dependencies
+between entries in symbolic substitution maps.
+<br>
+(Jean-Paul Pelteret, 2019/05/05)
index 02c581c0dc75893d02e0cb70df91667cfc0d2b07..eff52e923bf1916879f623a259cd552f9b54b06a 100644 (file)
@@ -597,6 +597,90 @@ namespace Differentiation
 
     //@}
 
+    /**
+     * @name Symbol substitution and evaluation
+     */
+    //@{
+
+    /**
+     * Return a substitution map that has any explicit interdependencies between
+     * the entries of the input @p substitution_map resolved.
+     *
+     * The @p force_cyclic_dependency_resolution flag exists to ensure, if
+     * desired, that no cyclic dependencies can exist in the returned map.
+     * If a cyclic dependency exists in the input substitution map,
+     * @p substitution_map, then with this flag set to <tt>true</tt> the
+     * dependency cycle is broken by a dictionary-ordered substitution.
+     * For example, if the substitution map contains two entries
+     * <tt>map["a"] -> "b"</tt> and <tt>map["b"] -> "a"</tt>, then the result
+     * of calling this function would be a map with the elements
+     * <tt>map["a"] -> "a"</tt> and <tt>map["b"] -> "a"</tt>.
+     *
+     * If one symbol is an explicit function of another, and it is desired that
+     * all their values are completely resolved, then it may be necessary to
+     * perform substitution a number of times before the result is finalized.
+     * This function performs substitution sweeps for a set of symbolic
+     * variables until all explicit relationships between the symbols in the map
+     * have been resolved. Whether each entry returns a symbolic or real value
+     * depends on the nature of the values stored in the substitution map. If
+     * the values associated with a key are also symbolic then the returned
+     * result may still be symbolic in nature. The terminal result of using the
+     * input substitution map, @p symbol_values, is then guaranteed to be
+     * rendered by a single substitition of the returned dependency-resolved
+     * map.
+     *
+     * Example: If <tt>map["a"] -> 1</tt> and
+     * <tt>map["b"] -> "a"+ 2</tt>, then then the function $f(a,b(a)) = a+b$
+     * will be evaluated and the result $f\vert_{a=1,b=a+2} = 3+a$ is determined
+     * upon the completion of the first sweep. A second sweep is therefore
+     * necessary to resolve the final symbol, and the returned value is
+     * ultimately $f = [3+a]_{a=1} = 4$. By resolving the explicit relationships
+     * between all symbols in the map, we determine that <tt>map["a"] -> 1</tt>
+     * and <tt>map["b"] -> 1 + 2 = 3</tt> and thus, using only one substitution,
+     * that $f = a+b = 1 + 3 = 4$.
+     */
+    types::substitution_map
+    resolve_explicit_dependencies(
+      const types::substitution_map &substitution_map,
+      const bool force_cyclic_dependency_resolution = false);
+
+    /**
+     * Return a substitution map that has any explicit interdependencies between
+     * the entries of the map generated by the paired elements in the
+     * @p symbol_values vector resolved.
+     * The @p force_cyclic_dependency_resolution exists to ensure, if desired,
+     * that no cyclic dependencies can exist in the returned map.
+     *
+     * This function performs substitution sweeps for a set of symbolic
+     * variables until all explicit relationships between the symbols in the map
+     * have been resolved. Whether each entry returns a symbolic or real value
+     * depends on the chosen ValueType and the values represented therein. If
+     * the ValueType is another Expression, and they contain symbols then
+     * the returned result may still be symbolic in nature.
+     *
+     * For an example of what this function does, see the documentation for the
+     * other @ref resolve_explicit_dependencies(const
+     * types::substitution_map &) function.
+     *
+     * @tparam ExpressionType A type that represents a symbolic expression.
+     *         The Differentiation::SD::Expression class is often suitable for
+     *         this purpose, although if the @p ValueType is not supported
+     *         by this class then a user-defined @p ExpressionType should be
+     *         used.
+     * @tparam ValueType A type that corresponds to the @p value that the
+     *         @p symbol is to represent. Although it is typically
+     *         arithmetic in nature, it may also represent another symbolic
+     *         expression type or be a special type that a user-defined
+     *         @p ExpressionType can be constructed from.
+     */
+    template <typename ExpressionType, typename ValueType>
+    types::substitution_map
+    resolve_explicit_dependencies(
+      const std::vector<std::pair<ExpressionType, ValueType>> &symbol_values,
+      const bool force_cyclic_dependency_resolution = false);
+
+    //@}
+
   } // namespace SD
 } // namespace Differentiation
 
@@ -859,6 +943,20 @@ namespace Differentiation
                               other_substitution_maps_in...);
     }
 
+
+    /* ---------------- Symbol substitution and evaluation --------------*/
+
+
+    template <typename ExpressionType, typename ValueType>
+    types::substitution_map
+    resolve_explicit_dependencies(
+      const std::vector<std::pair<ExpressionType, ValueType>> &symbol_values,
+      const bool force_cyclic_dependency_resolution)
+    {
+      return resolve_explicit_dependencies(make_substitution_map(symbol_values),
+                                           force_cyclic_dependency_resolution);
+    }
+
   } // namespace SD
 } // namespace Differentiation
 
index bf24b4b4a55564ae77a30bab271de7cc80dc1ecc..e2d8d8ca9e2cbd3746f5df20cb9e4b736118bf9b 100644 (file)
@@ -156,6 +156,61 @@ namespace Differentiation
         }
     }
 
+
+    /* ---------------- Symbol substitution and evaluation --------------*/
+
+
+    types::substitution_map
+    resolve_explicit_dependencies(const types::substitution_map &symbol_values,
+                                  const bool force_cyclic_dependency_resolution)
+    {
+      types::substitution_map symbol_values_resolved = symbol_values;
+      const std::size_t       size                   = symbol_values.size();
+      (void)size;
+      for (auto &entry : symbol_values_resolved)
+        {
+          // Perform dictionary-based substitution to
+          // resolve all explicit relations in a map.
+          // Instead of checking by value (and thus having
+          // to store a temporary value), we check to see
+          // if the hash of the map entry changes.
+          Expression & out = entry.second;
+          SE::hash_t   hash_old;
+          SE::hash_t   hash_new = out.get_RCP()->hash();
+          unsigned int iter     = 0;
+          do
+            {
+              // Write the substituted value straight back
+              // into the map.
+              if (force_cyclic_dependency_resolution)
+                {
+                  // Here we substitute in symbol_values_resolved instead of
+                  // symbol_values, in order to break any cyclic dependencies.
+                  // The earlier entries in the dictionary are in this way
+                  // guarenteed to be resolved before any subsequent entries,
+                  // thereby breaking the dependency loop.
+                  out = out.substitute(symbol_values_resolved);
+                }
+              else
+                {
+                  out = out.substitute(symbol_values);
+                }
+
+              // Compute and store the hash of the new object
+              hash_old = std::move(hash_new);
+              hash_new = out.get_RCP()->hash();
+              AssertThrow(
+                iter < size,
+                ExcMessage(
+                  "Unresolvable cyclic dependency detected in substitution map."));
+              ++iter;
+            }
+          while (hash_new != hash_old);
+        }
+
+      return symbol_values_resolved;
+    }
+
   } // namespace SD
 } // namespace Differentiation
 
diff --git a/tests/symengine/substitution_maps_cyclic_dependencies_01.cc b/tests/symengine/substitution_maps_cyclic_dependencies_01.cc
new file mode 100644 (file)
index 0000000..100f487
--- /dev/null
@@ -0,0 +1,61 @@
+// ---------------------------------------------------------------------
+//
+// Copyright (C) 2019 by the deal.II authors
+//
+// This file is part of the deal.II library.
+//
+// The deal.II library is free software; you can use it, redistribute
+// it, and/or modify it under the terms of the GNU Lesser General
+// Public License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// The full text of the license can be found in the file LICENSE at
+// the top level of the deal.II distribution.
+//
+// ---------------------------------------------------------------------
+
+
+// Check that cyclic dependencies in a substitution map are are detected when
+// using the resolve_explicit_dependencies() function.
+
+#include <deal.II/differentiation/sd.h>
+
+#include <iostream>
+#include <string>
+
+#include "../tests.h"
+
+using namespace dealii;
+namespace SD = Differentiation::SD;
+
+int
+main()
+{
+  initlog();
+
+  // Introduce a cyclic dependency into a map.
+  // Here we set a==b and b==a.
+  const SD::types::substitution_map sub_vals_unresolved =
+    SD::make_substitution_map(
+      std::make_pair(SD::Expression("a"), SD::Expression("b")),
+      std::make_pair(SD::Expression("b"), SD::Expression("a")));
+  std::cout << "Original map:" << std::endl;
+  SD::Utilities::print_substitution_map(std::cout, sub_vals_unresolved);
+
+  deal_II_exceptions::disable_abort_on_exception();
+  try
+    {
+      const SD::types::substitution_map sub_vals_resolved =
+        SD::resolve_explicit_dependencies(sub_vals_unresolved);
+      std::cout << "Resolved map:" << std::endl;
+      SD::Utilities::print_substitution_map(std::cout, sub_vals_resolved);
+      deallog
+        << "Cyclic dependency in substitution map was unexpectedly resolved."
+        << std::endl;
+    }
+  catch (...)
+    {
+      deallog << "Detected cyclic dependency in substitution map." << std::endl;
+    }
+
+  deallog << "OK" << std::endl;
+}
diff --git a/tests/symengine/substitution_maps_cyclic_dependencies_01.output b/tests/symengine/substitution_maps_cyclic_dependencies_01.output
new file mode 100644 (file)
index 0000000..4d97f5d
--- /dev/null
@@ -0,0 +1,3 @@
+
+DEAL::Detected cyclic dependency in substitution map.
+DEAL::OK
diff --git a/tests/symengine/substitution_maps_cyclic_dependencies_02.cc b/tests/symengine/substitution_maps_cyclic_dependencies_02.cc
new file mode 100644 (file)
index 0000000..0821213
--- /dev/null
@@ -0,0 +1,53 @@
+// ---------------------------------------------------------------------
+//
+// Copyright (C) 2019 by the deal.II authors
+//
+// This file is part of the deal.II library.
+//
+// The deal.II library is free software; you can use it, redistribute
+// it, and/or modify it under the terms of the GNU Lesser General
+// Public License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// The full text of the license can be found in the file LICENSE at
+// the top level of the deal.II distribution.
+//
+// ---------------------------------------------------------------------
+
+
+// Check that, when the correct flag is set, cyclic dependencies in a
+// substitution map are resolved in dictionary order, and do not cause an
+// infinite loop in the resolve_explicit_dependencies() function.
+
+#include <deal.II/differentiation/sd.h>
+
+#include <iostream>
+#include <string>
+
+#include "../tests.h"
+
+using namespace dealii;
+namespace SD = Differentiation::SD;
+
+int
+main()
+{
+  initlog();
+
+  // Introduce a cyclic dependency into a map.
+  // Here we set a==b and b==a.
+  const SD::types::substitution_map sub_vals_unresolved =
+    SD::make_substitution_map(
+      std::make_pair(SD::Expression("a"), SD::Expression("b")),
+      std::make_pair(SD::Expression("b"), SD::Expression("a")));
+  std::cout << "Original map:" << std::endl;
+  SD::Utilities::print_substitution_map(std::cout, sub_vals_unresolved);
+
+  const bool                        force_cyclic_dependency_resolution = true;
+  const SD::types::substitution_map sub_vals_resolved =
+    SD::resolve_explicit_dependencies(sub_vals_unresolved,
+                                      force_cyclic_dependency_resolution);
+  std::cout << "Resolved map:" << std::endl;
+  SD::Utilities::print_substitution_map(std::cout, sub_vals_resolved);
+
+  deallog << "OK" << std::endl;
+}
diff --git a/tests/symengine/substitution_maps_cyclic_dependencies_02.output b/tests/symengine/substitution_maps_cyclic_dependencies_02.output
new file mode 100644 (file)
index 0000000..0fd8fc1
--- /dev/null
@@ -0,0 +1,2 @@
+
+DEAL::OK

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.