piranha  0.10
lambdify.hpp
1 /* Copyright 2009-2017 Francesco Biscani (bluescarni@gmail.com)
2 
3 This file is part of the Piranha library.
4 
5 The Piranha library is free software; you can redistribute it and/or modify
6 it under the terms of either:
7 
8  * the GNU Lesser General Public License as published by the Free
9  Software Foundation; either version 3 of the License, or (at your
10  option) any later version.
11 
12 or
13 
14  * the GNU General Public License as published by the Free Software
15  Foundation; either version 3 of the License, or (at your option) any
16  later version.
17 
18 or both in parallel, as here.
19 
20 The Piranha library is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23 for more details.
24 
25 You should have received copies of the GNU General Public License and the
26 GNU Lesser General Public License along with the Piranha library. If not,
27 see https://www.gnu.org/licenses/. */
28 
29 #ifndef PIRANHA_LAMBDIFY_HPP
30 #define PIRANHA_LAMBDIFY_HPP
31 
32 #include <algorithm>
33 #include <functional>
34 #include <initializer_list>
35 #include <iterator>
36 #include <memory>
37 #include <stdexcept>
38 #include <string>
39 #include <type_traits>
40 #include <unordered_map>
41 #include <utility>
42 #include <vector>
43 
44 #include <piranha/detail/sfinae_types.hpp>
45 #include <piranha/exceptions.hpp>
46 #include <piranha/math.hpp>
47 #include <piranha/symbol_utils.hpp>
48 #include <piranha/type_traits.hpp>
49 
50 namespace piranha
51 {
52 
53 namespace detail
54 {
55 
56 // Requirements for the lambdified template parameters (after decay).
57 template <typename T, typename U>
58 using math_lambdified_reqs = std::integral_constant<
59  bool, conjunction<is_evaluable<T, U>, std::is_copy_constructible<T>, std::is_move_constructible<T>>::value>;
60 }
61 
62 namespace math
63 {
64 
66 
87 template <typename T, typename U>
89 {
90  static_assert(std::is_same<T, typename std::decay<T>::type>::value, "Invalid type.");
91  static_assert(std::is_same<U, typename std::decay<U>::type>::value, "Invalid type.");
92  static_assert(detail::math_lambdified_reqs<T, U>::value, "Invalid types.");
93  // Constructor implementation.
94  void construct()
95  {
96  // Check if there are duplicates.
97  auto names_copy(m_names);
98  std::sort(names_copy.begin(), names_copy.end());
99  if (std::unique(names_copy.begin(), names_copy.end()) != names_copy.end()) {
100  piranha_throw(std::invalid_argument, "the list of evaluation symbols contains duplicates");
101  }
102  // Fill in the eval dict and the vector of pointers, and make sure
103  // that m_extra_map does not contain anything that is already in m_names.
104  for (const auto &s : m_names) {
105  if (m_extra_map.find(s) != m_extra_map.end()) {
106  piranha_throw(std::invalid_argument,
107  "the extra symbols map contains symbol '" + s
108  + "', which is already in the symbol list used for the construction "
109  "of the lambdified object");
110  }
111  const auto ret = m_eval_dict.emplace(std::make_pair(s, U{}));
112  piranha_assert(ret.second);
113  m_ptrs.push_back(std::addressof(ret.first->second));
114  }
115  // Fill in the extra symbols.
116  for (const auto &p : m_extra_map) {
117  const auto ret = m_eval_dict.emplace(std::make_pair(p.first, U{}));
118  piranha_assert(ret.second);
119  m_ptrs.push_back(std::addressof(ret.first->second));
120  }
121  }
122  // Reconstruct the vector of pointers following copy or move construction.
123  void reconstruct_ptrs()
124  {
125  // m_ptrs must be empty because we assume this method is called only as part of
126  // the copy/move ctors.
127  piranha_assert(m_ptrs.empty());
128  piranha_assert(m_eval_dict.size() >= m_extra_map.size()
129  && m_names.size() == m_eval_dict.size() - m_extra_map.size());
130  // Take care first of the positional argument.
131  for (const auto &s : m_names) {
132  piranha_assert(m_eval_dict.count(s) == 1u);
133  m_ptrs.push_back(std::addressof(m_eval_dict.find(s)->second));
134  }
135  // The extra symbols.
136  for (const auto &p : m_extra_map) {
137  piranha_assert(m_eval_dict.count(p.first) == 1u);
138  m_ptrs.push_back(std::addressof(m_eval_dict.find(p.first)->second));
139  }
140  // Make sure the sizes are consistent.
141  piranha_assert(m_ptrs.size() == static_cast<decltype(m_ptrs.size())>(m_names.size()) + m_extra_map.size());
142  }
143 
144 public:
146 
150  using eval_type = decltype(math::evaluate(std::declval<const T &>(), std::declval<const symbol_fmap<U> &>()));
152 
155  using extra_map_type = std::unordered_map<std::string, std::function<U(const std::vector<U> &)>>;
157 
187  explicit lambdified(const T &x, const std::vector<std::string> &names, extra_map_type extra_map = {})
188  : m_x(x), m_names(names), m_extra_map(extra_map)
189  {
190  construct();
191  }
193 
209  explicit lambdified(T &&x, const std::vector<std::string> &names, extra_map_type extra_map = {})
210  : m_x(std::move(x)), m_names(names), m_extra_map(extra_map)
211  {
212  construct();
213  }
215 
220  lambdified(const lambdified &other)
221  : m_x(other.m_x), m_names(other.m_names), m_eval_dict(other.m_eval_dict), m_extra_map(other.m_extra_map)
222  {
223  reconstruct_ptrs();
224  }
226 
232  : m_x(std::move(other.m_x)), m_names(std::move(other.m_names)), m_eval_dict(std::move(other.m_eval_dict)),
233  m_extra_map(std::move(other.m_extra_map))
234  {
235  // NOTE: it looks like we cannot be sure the moved-in pointers are still valid.
236  // Let's just make sure.
237  reconstruct_ptrs();
238  }
239 
240 private:
241  lambdified &operator=(const lambdified &) = delete;
242  lambdified &operator=(lambdified &&) = delete;
243 
244 public:
246 
269  eval_type operator()(const std::vector<U> &values)
270  {
271  if (unlikely(values.size() != m_eval_dict.size() - m_extra_map.size())) {
272  piranha_throw(std::invalid_argument, "the size of the vector of evaluation values does not "
273  "match the size of the symbol list used during construction");
274  }
275  piranha_assert(values.size() == m_names.size());
276  decltype(values.size()) i = 0u;
277  for (; i < values.size(); ++i) {
278  auto ptr = m_ptrs[static_cast<decltype(m_ptrs.size())>(i)];
279  piranha_assert(
280  ptr == std::addressof(m_eval_dict.find(m_names[static_cast<decltype(m_names.size())>(i)])->second));
281  *ptr = values[i];
282  }
283  // NOTE: here it is crucial that the iteration order on m_extra_map is the same
284  // as it was during the construction of the m_ptrs vector, otherwise we are associating
285  // values to the wrong symbols. Luckily, it seems like iterating on a const unordered_map
286  // multiple times yields the same order:
287  // http://stackoverflow.com/questions/18301302/is-forauto-i-unordered-map-guaranteed-to-have-the-same-order-every-time
288  // https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/kHYFUhsauhU
289  for (const auto &p : m_extra_map) {
290  piranha_assert(i < m_ptrs.size());
291  auto ptr = m_ptrs[static_cast<decltype(m_ptrs.size())>(i)];
292  piranha_assert(ptr == std::addressof(m_eval_dict.find(p.first)->second));
293  *ptr = p.second(values);
294  ++i;
295  }
296  // NOTE: of course, this will have to be fixed in the rewrite.
297  return math::evaluate(m_x, symbol_fmap<U>{m_eval_dict.begin(), m_eval_dict.end()});
298  }
300 
304  const T &get_evaluable() const
305  {
306  return m_x;
307  }
309 
312  const std::vector<std::string> &get_names() const
313  {
314  return m_names;
315  }
317 
322  std::vector<std::string> get_extra_names() const
323  {
324  std::vector<std::string> retval;
325  std::transform(m_extra_map.begin(), m_extra_map.end(), std::back_inserter(retval),
326  [](const typename extra_map_type::value_type &p) { return p.first; });
327  return retval;
328  }
329 
330 private:
331  T m_x;
332  std::vector<std::string> m_names;
333  std::unordered_map<std::string, U> m_eval_dict;
334  std::vector<U *> m_ptrs;
335  extra_map_type m_extra_map;
336 };
337 }
338 
339 namespace detail
340 {
341 
342 // Enabler for lambdify().
343 template <typename T, typename U>
344 using math_lambdify_type =
345  typename std::enable_if<math_lambdified_reqs<typename std::decay<T>::type, typename std::decay<U>::type>::value,
346  math::lambdified<typename std::decay<T>::type, typename std::decay<U>::type>>::type;
347 
348 // Shortcut for the extra symbols map type.
349 template <typename T, typename U>
350 using math_lambdify_extra_map_type = typename math_lambdify_type<T, U>::extra_map_type;
351 }
352 
353 namespace math
354 {
355 
357 
396 template <typename U, typename T>
397 inline detail::math_lambdify_type<T, U> lambdify(T &&x, const std::vector<std::string> &names,
398  detail::math_lambdify_extra_map_type<T, U> extra_map = {})
399 {
400  return lambdified<typename std::decay<T>::type, typename std::decay<U>::type>(std::forward<T>(x), names, extra_map);
401 }
402 }
403 
405 
411 template <typename T, typename U>
412 class has_lambdify : detail::sfinae_types
413 {
414  using Td = typename std::decay<T>::type;
415  using Ud = typename std::decay<U>::type;
416  template <typename T1, typename U1>
417  static auto test(const T1 &x, const U1 &) -> decltype(math::lambdify<U1>(x, {}), void(), yes());
418  static no test(...);
419  static const bool implementation_defined
420  = std::is_same<yes, decltype(test(std::declval<const Td &>(), std::declval<const Ud &>()))>::value;
421 
422 public:
424  static const bool value = implementation_defined;
425 };
426 
427 template <typename T, typename U>
428 const bool has_lambdify<T, U>::value;
429 }
430 
431 #endif
decltype(math::evaluate(std::declval< const T & >(), std::declval< const symbol_fmap< U > & >())) eval_type
Evaluation type.
Definition: lambdify.hpp:150
const std::vector< std::string > & get_names() const
Get evaluation names.
Definition: lambdify.hpp:312
lambdified(const T &x, const std::vector< std::string > &names, extra_map_type extra_map={})
Constructor.
Definition: lambdify.hpp:187
Detect the presence of piranha::math::lambdify().
Definition: lambdify.hpp:412
boost::container::flat_map< std::string, T > symbol_fmap
Flat map of symbols.
static const bool value
Value of the type trait.
Definition: lambdify.hpp:424
Exceptions.
lambdified(lambdified &&other)
Move constructor.
Definition: lambdify.hpp:231
std::unordered_map< std::string, std::function< U(const std::vector< U > &)> > extra_map_type
The map type for the custom evaluation of symbols.
Definition: lambdify.hpp:155
STL namespace.
lambdified(T &&x, const std::vector< std::string > &names, extra_map_type extra_map={})
Constructor (move overload).
Definition: lambdify.hpp:209
math_evaluate_t< T, U > evaluate(const T &x, const symbol_fmap< U > &dict)
Evaluation.
Definition: math.hpp:831
#define piranha_throw(exception_type,...)
Exception-throwing macro.
Definition: exceptions.hpp:118
const T & get_evaluable() const
Get evaluation object.
Definition: lambdify.hpp:304
Functor interface for piranha::math::evaluate().
Definition: lambdify.hpp:88
lambdified(const lambdified &other)
Copy constructor.
Definition: lambdify.hpp:220
std::vector< std::string > get_extra_names() const
Get names of the symbols in the extra map.
Definition: lambdify.hpp:322
Root piranha namespace.
Definition: array_key.hpp:52
Type traits.
detail::math_lambdify_type< T, U > lambdify(T &&x, const std::vector< std::string > &names, detail::math_lambdify_extra_map_type< T, U > extra_map={})
Create a functor interface for piranha::math::evaluate().
Definition: lambdify.hpp:397
eval_type operator()(const std::vector< U > &values)
Evaluation.
Definition: lambdify.hpp:269