#ifndef ALGOIM_UVECTOR_HPP #define ALGOIM_UVECTOR_HPP // algoim::uvector implements a vector with compile-time known extent, including // arithmetic expressions and various utility methods. It could be viewed as, e.g., // a light-weight, self-contained, re-implementation of blitz's TinyVector, // (https://en.wikipedia.org/wiki/Blitz%2B%2B), using more modern C++17/20 // techniques. #include #include #include #include #include #include "real.hpp" namespace algoim { template class uvector; template struct uvector_expr { T eval; }; namespace detail { template constexpr auto eval(const uvector& e, int i) { return e(i); } template constexpr auto eval(const uvector_expr& expr, int i) { return expr.eval(i); } template constexpr auto eval(const E& e, int i) { return e; } template constexpr auto make_uvector_expr(const T& eval) { return uvector_expr{eval}; } template struct extent_of { static constexpr int value = 0; }; template struct extent_of> { static constexpr int value = N; }; template struct extent_of> { static constexpr int value = N; }; template using extent = std::integral_constant>::value>; } // namespace detail // binary uvector expressions #define algoim_decl_binary_op(op) \ template ::value > 0) || (detail::extent::value > 0), bool> = true> \ auto operator op(E1&& e1, E2&& e2) \ { \ static_assert(detail::extent::value == 0 || detail::extent::value == 0 \ || detail::extent::value == detail::extent::value, \ "incompatible extents in algoim::uvector expression"); \ constexpr int N = std::max(detail::extent::value, detail::extent::value); \ if constexpr (std::is_lvalue_reference::value) \ if constexpr (std::is_lvalue_reference::value) \ return detail::make_uvector_expr([&e1, &e2](int i) { return detail::eval(e1, i) op detail::eval(e2, i); }); \ else \ return detail::make_uvector_expr([&e1, e2](int i) { return detail::eval(e1, i) op detail::eval(e2, i); }); \ else if constexpr (std::is_lvalue_reference::value) \ return detail::make_uvector_expr([e1, &e2](int i) { return detail::eval(e1, i) op detail::eval(e2, i); }); \ else \ return detail::make_uvector_expr([e1, e2](int i) { return detail::eval(e1, i) op detail::eval(e2, i); }); \ } algoim_decl_binary_op(+) algoim_decl_binary_op(-) algoim_decl_binary_op(*) algoim_decl_binary_op(/) algoim_decl_binary_op(<) algoim_decl_binary_op(<=) algoim_decl_binary_op(>) algoim_decl_binary_op(>=) algoim_decl_binary_op(==) algoim_decl_binary_op(!=) algoim_decl_binary_op(%) #undef algoim_decl_binary_op // unary negation operator for uvector or uvector expressions template ::value >= 1, bool> = true> auto operator-(const E& e) { constexpr int N = detail::extent::value; if constexpr (std::is_lvalue_reference::value) return detail::make_uvector_expr([&e](int i) { return -detail::eval(e, i); }); else return detail::make_uvector_expr([e](int i) { return -detail::eval(e, i); }); } // abs of uvector or uvector expressions template ::value >= 1, bool> = true> auto abs(const E& e) { using std::abs; constexpr int N = detail::extent::value; if constexpr (std::is_lvalue_reference::value) return detail::make_uvector_expr([&e](int i) { return abs(detail::eval(e, i)); }); else return detail::make_uvector_expr([e](int i) { return abs(detail::eval(e, i)); }); } // uvector class template class uvector { T data_[N]; public: // default constructor zero initialises uvector() { memset(data_, 0, sizeof(data_)); } template = 2 && sizeof...(U) == N, bool> = true> explicit uvector(U&&... args) : data_{static_cast(std::forward(args))...} { } template uvector(const U& x) { *this = x; } T& operator()(int i) { return data_[i]; } const T& operator()(int i) const { return data_[i]; } T* data() { return data_; } const T* data() const { return data_; } // assignment and update operators, e.g., operator=, operator+=, operator*=, etc. #define algoim_decl_assign_op(op) \ template \ uvector& operator op(const E & x) \ { \ static_assert(detail::extent::value == 0 || detail::extent::value == N, \ "incompatible extents in algoim::uvector expression"); \ for (int i = 0; i < N; ++i) data_[i] op detail::eval(x, i); \ return *this; \ } algoim_decl_assign_op(=) algoim_decl_assign_op(+=) algoim_decl_assign_op(-=) algoim_decl_assign_op(*=) algoim_decl_assign_op(/=) #undef algoim_decl_assign_op }; using uvector3f = uvector; using uvector2f = uvector; using uvector3i = uvector; using uvector2i = uvector; using uvector3 = uvector3f; using uvector2 = uvector2f; // some methods, particularly those which remove components of a vector, benefit // from having a zero-length uvector; it is simply an empty class template class uvector { }; template ::value >= 1, bool> = true> std::ostream& operator<<(std::ostream& o, const T& u) { constexpr int N = detail::extent::value; o << '('; for (int i = 0; i < N; ++i) o << detail::eval(u, i) << (i + 1 < N ? ',' : ')'); return o; } // minimum component of a uvector or uvector expression template ::value >= 1, bool> = true> auto min(const T& u) { constexpr int N = detail::extent::value; auto x = detail::eval(u, 0); for (int i = 1; i < N; ++i) { auto y = detail::eval(u, i); if (y < x) x = y; } return x; } // maximum component of a uvector or uvector expression template ::value >= 1, bool> = true> auto max(const T& u) { constexpr int N = detail::extent::value; auto x = detail::eval(u, 0); for (int i = 1; i < N; ++i) { auto y = detail::eval(u, i); if (y > x) x = y; } return x; } // index where the minimum of a uvector or uvector expression is attained; if the minimum // is attained at multiple places, the smallest corresponding index is returned template ::value >= 1, bool> = true> int argmin(const T& u) { constexpr int N = detail::extent::value; auto x = detail::eval(u, 0); int ind = 0; for (int i = 1; i < N; ++i) { auto y = detail::eval(u, i); if (y < x) { x = y; ind = i; } } return ind; } // index where the maximum of a uvector or uvector expression is attained; if the maximum // is attained at multiple places, the smallest corresponding index is returned template ::value >= 1, bool> = true> int argmax(const T& u) { constexpr int N = detail::extent::value; auto x = detail::eval(u, 0); int ind = 0; for (int i = 1; i < N; ++i) { auto y = detail::eval(u, i); if (y > x) { x = y; ind = i; } } return ind; } // sum of components of a uvector or uvector expression template ::value >= 1, bool> = true> auto sum(const T& u) { constexpr int N = detail::extent::value; auto x = detail::eval(u, 0); for (int i = 1; i < N; ++i) x += detail::eval(u, i); return x; } // product of components of a uvector or uvector expression template ::value >= 1, bool> = true> auto prod(const T& u) { constexpr int N = detail::extent::value; auto x = detail::eval(u, 0); for (int i = 1; i < N; ++i) x *= detail::eval(u, i); return x; } // product of components of a uvector or uvector expression, excluding // a given index; the empty product returns one template ::value >= 1, bool> = true> auto prod(const T& u, int excluded_index) { constexpr int N = detail::extent::value; auto x = detail::eval(u, 0); // deduce return type x = 1; for (int i = 0; i < N; ++i) if (i != excluded_index) x *= detail::eval(u, i); return x; } // dot product of two uvector or uvector expressions template < typename E1, typename E2, std::enable_if_t::value == detail::extent::value && detail::extent::value >= 1, bool> = true> auto dot(const E1& u, const E2& v) { constexpr int N = detail::extent::value; auto x = detail::eval(u, 0) * detail::eval(v, 0); for (int i = 1; i < N; ++i) x += detail::eval(u, i) * detail::eval(v, i); return x; } // cross product of two uvector3 or uvector3 expressions template ::value == 3 && detail::extent::value == 3, bool> = true> auto cross(const E1& u, const E2& v) { return uvector(detail::eval(u, 1) * detail::eval(v, 2) - detail::eval(u, 2) * detail::eval(v, 1), detail::eval(u, 2) * detail::eval(v, 0) - detail::eval(u, 0) * detail::eval(v, 2), detail::eval(u, 0) * detail::eval(v, 1) - detail::eval(u, 1) * detail::eval(v, 0)); } // component-wise max of two uvectors template auto max(const uvector& u, const uvector& v) { using std::max; uvector x; for (int i = 0; i < N; ++i) x(i) = max(u(i), v(i)); return x; } // returns false iff all components of u yield false when typecast to a bool template ::value >= 1, bool> = true> bool any(const T& u) { constexpr int N = detail::extent::value; bool x = false; for (int i = 0; i < N; ++i) x |= static_cast(detail::eval(u, i)); return x; } // returns true iff all components of u yield true when typecast to a bool template ::value >= 1, bool> = true> bool all(const T& u) { constexpr int N = detail::extent::value; bool x = true; for (int i = 0; i < N; ++i) x &= static_cast(detail::eval(u, i)); return x; } // squared Euclidean norm of a uvector or uvector expression template ::value >= 1, bool> = true> auto sqrnorm(const T& u) { return dot(u, u); } // Euclidean norm of a uvector or uvector expression template ::value >= 1, bool> = true> auto norm(const T& u) { using std::sqrt; return sqrt(sqrnorm(u)); } // alter the extent of a uvector by truncation or zero backfill template uvector alter_extent(const uvector& u) { uvector v; for (int i = 0; i < (M < N ? M : N); ++i) // truncate if M < N v(i) = u(i); for (int i = N; i < M; ++i) // backfill with zeros if M > N memset(&v(i), 0, sizeof(v(i))); return v; } // remove a component from a uvector template uvector remove_component(const uvector& u, int index) { uvector v; if constexpr (N > 1) for (int i = 0; i < N - 1; ++i) v(i) = u(i < index ? i : i + 1); return v; } // add a component to a vector, consistent with remove_component semantics template uvector add_component(const uvector& u, int index, T value) { if constexpr (N == 0) return uvector(value); else { uvector v; for (int i = 0; i < N + 1; ++i) v(i) = i < index ? u(i) : (i == index ? value : u(i - 1)); return v; } } // set the i'th component of a uvector to a given value template uvector set_component(uvector u, int i, T value) { u(i) = value; return u; } // increment the i'th component of a uvector by a given value template uvector inc_component(uvector u, int i, T value) { u(i) += value; return u; } } // namespace algoim #endif