self_evaluation.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. // Copyright (C) 2016-2018 T. Zachary Laine
  2. //
  3. // Distributed under the Boost Software License, Version 1.0. (See
  4. // accompanying file LICENSE_1_0.txt or copy at
  5. // http://www.boost.org/LICENSE_1_0.txt)
  6. //[ self_evaluation
  7. #include <boost/yap/expression.hpp>
  8. #include <boost/optional.hpp>
  9. #include <boost/hana/fold.hpp>
  10. #include <boost/hana/maximum.hpp>
  11. #include <algorithm>
  12. #include <cassert>
  13. #include <iostream>
  14. #include <vector>
  15. // A super-basic matrix type, and a few associated operations.
  16. struct matrix
  17. {
  18. matrix() : values_(), rows_(0), cols_(0) {}
  19. matrix(int rows, int cols) : values_(rows * cols), rows_(rows), cols_(cols)
  20. {
  21. assert(0 < rows);
  22. assert(0 < cols);
  23. }
  24. int rows() const { return rows_; }
  25. int cols() const { return cols_; }
  26. double operator()(int r, int c) const
  27. { return values_[r * cols_ + c]; }
  28. double & operator()(int r, int c)
  29. { return values_[r * cols_ + c]; }
  30. private:
  31. std::vector<double> values_;
  32. int rows_;
  33. int cols_;
  34. };
  35. matrix operator*(matrix const & lhs, double x)
  36. {
  37. matrix retval = lhs;
  38. for (int i = 0; i < retval.rows(); ++i) {
  39. for (int j = 0; j < retval.cols(); ++j) {
  40. retval(i, j) *= x;
  41. }
  42. }
  43. return retval;
  44. }
  45. matrix operator*(double x, matrix const & lhs) { return lhs * x; }
  46. matrix operator+(matrix const & lhs, matrix const & rhs)
  47. {
  48. assert(lhs.rows() == rhs.rows());
  49. assert(lhs.cols() == rhs.cols());
  50. matrix retval = lhs;
  51. for (int i = 0; i < retval.rows(); ++i) {
  52. for (int j = 0; j < retval.cols(); ++j) {
  53. retval(i, j) += rhs(i, j);
  54. }
  55. }
  56. return retval;
  57. }
  58. // daxpy() means Double-precision AX Plus Y. This crazy name comes from BLAS.
  59. // It is more efficient than a naive implementation, because it does not
  60. // create temporaries. The covnention of using Y as an out-parameter comes
  61. // from FORTRAN BLAS.
  62. matrix & daxpy(double a, matrix const & x, matrix & y)
  63. {
  64. assert(x.rows() == y.rows());
  65. assert(x.cols() == y.cols());
  66. for (int i = 0; i < y.rows(); ++i) {
  67. for (int j = 0; j < y.cols(); ++j) {
  68. y(i, j) += a * x(i, j);
  69. }
  70. }
  71. return y;
  72. }
  73. template <boost::yap::expr_kind Kind, typename Tuple>
  74. struct self_evaluating_expr;
  75. template <boost::yap::expr_kind Kind, typename Tuple>
  76. auto evaluate_matrix_expr(self_evaluating_expr<Kind, Tuple> const & expr);
  77. // This is the primary template for our expression template. If you assign a
  78. // self_evaluating_expr to a matrix, its conversion operator transforms and
  79. // evaluates the expression with a call to evaluate_matrix_expr().
  80. template <boost::yap::expr_kind Kind, typename Tuple>
  81. struct self_evaluating_expr
  82. {
  83. operator auto() const;
  84. static const boost::yap::expr_kind kind = Kind;
  85. Tuple elements;
  86. };
  87. // This is a specialization of our expression template for assignment
  88. // expressions. The destructor transforms and evaluates via a call to
  89. // evaluate_matrix_expr(), and then assigns the result to the variable on the
  90. // left side of the assignment.
  91. //
  92. // In a production implementation, you'd need to have specializations for
  93. // plus_assign, minus_assign, etc.
  94. template <typename Tuple>
  95. struct self_evaluating_expr<boost::yap::expr_kind::assign, Tuple>
  96. {
  97. ~self_evaluating_expr();
  98. static const boost::yap::expr_kind kind = boost::yap::expr_kind::assign;
  99. Tuple elements;
  100. };
  101. struct use_daxpy
  102. {
  103. // A plus-expression, which may be of the form double * matrix + matrix,
  104. // or may be something else. Since our daxpy() above requires a mutable
  105. // "y", we only need to match a mutable lvalue matrix reference here.
  106. template <typename Tuple>
  107. auto operator()(
  108. boost::yap::expr_tag<boost::yap::expr_kind::plus>,
  109. self_evaluating_expr<boost::yap::expr_kind::multiplies, Tuple> const & expr,
  110. matrix & m)
  111. {
  112. // Here, we transform the left-hand side into a pair if it's the
  113. // double * matrix operation we're looking for. Otherwise, we just
  114. // get a copy of the left side expression.
  115. //
  116. // Note that this is a bit of a cheat, done for clarity. If we pass a
  117. // larger expression that happens to contain a double * matrix
  118. // subexpression, that subexpression will be transformed into a tuple!
  119. // In production code, this transform should probably only be
  120. // performed on an expression with all terminal members.
  121. auto lhs = boost::yap::transform(
  122. expr,
  123. [](boost::yap::expr_tag<boost::yap::expr_kind::multiplies>,
  124. double d,
  125. matrix const & m) {
  126. return std::pair<double, matrix const &>(d, m);
  127. });
  128. // If we got back a copy of expr above, just re-construct the
  129. // expression this function mathes; in other words, do not effectively
  130. // transform anything. Otherwise, replace the expression matched by
  131. // this function with a call to daxpy().
  132. if constexpr (boost::yap::is_expr<decltype(lhs)>::value) {
  133. return expr + m;
  134. } else {
  135. return boost::yap::make_terminal(daxpy)(lhs.first, lhs.second, m);
  136. }
  137. }
  138. };
  139. // This is the heart of what self_evaluating_expr does. If we had other
  140. // optimizations/transformations we wanted to do, we'd put them in this
  141. // function, either before or after the use_daxpy transformation.
  142. template <boost::yap::expr_kind Kind, typename Tuple>
  143. auto evaluate_matrix_expr(self_evaluating_expr<Kind, Tuple> const & expr)
  144. {
  145. auto daxpy_form = boost::yap::transform(expr, use_daxpy{});
  146. return boost::yap::evaluate(daxpy_form);
  147. }
  148. template<boost::yap::expr_kind Kind, typename Tuple>
  149. self_evaluating_expr<Kind, Tuple>::operator auto() const
  150. {
  151. return evaluate_matrix_expr(*this);
  152. }
  153. template<typename Tuple>
  154. self_evaluating_expr<boost::yap::expr_kind::assign, Tuple>::
  155. ~self_evaluating_expr()
  156. {
  157. using namespace boost::hana::literals;
  158. boost::yap::evaluate(elements[0_c]) = evaluate_matrix_expr(elements[1_c]);
  159. }
  160. // In order to define the = operator with the semantics we want, it's
  161. // convenient to derive a terminal type from a terminal instantiation of
  162. // self_evaluating_expr. Note that we could have written a template
  163. // specialization here instead -- either one would work. That would of course
  164. // have required more typing.
  165. struct self_evaluating :
  166. self_evaluating_expr<
  167. boost::yap::expr_kind::terminal,
  168. boost::hana::tuple<matrix>
  169. >
  170. {
  171. self_evaluating() {}
  172. explicit self_evaluating(matrix m)
  173. { elements = boost::hana::tuple<matrix>(std::move(m)); }
  174. BOOST_YAP_USER_ASSIGN_OPERATOR(self_evaluating_expr, ::self_evaluating_expr);
  175. };
  176. BOOST_YAP_USER_BINARY_OPERATOR(plus, self_evaluating_expr, self_evaluating_expr)
  177. BOOST_YAP_USER_BINARY_OPERATOR(minus, self_evaluating_expr, self_evaluating_expr)
  178. BOOST_YAP_USER_BINARY_OPERATOR(multiplies, self_evaluating_expr, self_evaluating_expr)
  179. int main()
  180. {
  181. matrix identity(2, 2);
  182. identity(0, 0) = 1.0;
  183. identity(1, 1) = 1.0;
  184. // These are YAP-ified terminal expressions.
  185. self_evaluating m1(identity);
  186. self_evaluating m2(identity);
  187. self_evaluating m3(identity);
  188. // This transforms the YAP expression to use daxpy(), so it creates no
  189. // temporaries. The transform happens in the destructor of the
  190. // assignment-expression specialization of self_evaluating_expr.
  191. m1 = 3.0 * m2 + m3;
  192. // Same as above, except that it uses the matrix conversion operator on
  193. // the self_evaluating_expr primary template, because here we're assigning
  194. // a YAP expression to a non-YAP-ified matrix.
  195. matrix m_result_1 = 3.0 * m2 + m3;
  196. // Creates temporaries and does not use daxpy(), because the A * X + Y
  197. // pattern does not occur within the expression.
  198. matrix m_result_2 = 3.0 * m2;
  199. return 0;
  200. }
  201. //]