DNDSR 0.2.1
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
EigenUtil.hpp
Go to the documentation of this file.
1#pragma once
2/// @file EigenUtil.hpp
3/// @brief Eigen extensions: `to_string`, an fmt-safe wrapper, and fmt formatter
4/// specialisations for dense Eigen matrices.
5///
6/// The @ref DNDS::MatrixFMTSafe "MatrixFMTSafe" / @ref VectorFMTSafe helpers exist because modern fmtlib
7/// (with `fmt/ranges.h`) will detect `Eigen::Matrix` as a range and format
8/// it as `[a, b, c, ...]`, overriding the Eigen stream formatting that
9/// DNDSR wants. Wrapping Eigen types with these aliases hides the iterator
10/// interface from fmt and keeps the matrix pretty-print.
11
12#include "EigenPCH.hpp"
13
14#include "Defines.hpp"
15
16#include <fmt/core.h>
17#include <fmt/ostream.h>
18#include <fmt/format.h>
20#include "Vector.hpp"
21
22namespace DNDS
23{
24 /// @brief Render an `Eigen::DenseBase` to a string via `operator<<`.
25 /// @param precision Setprecision applied if `> 0`.
26 /// @param scientific Whether to use `std::scientific` notation.
27 // TODO: lessen copying chance?
28 template <class dir>
29 std::string to_string(const Eigen::DenseBase<dir> &v,
30 int precision = 5,
31 bool scientific = false)
32 {
33 std::stringstream ss;
34 if (precision > 0)
35 ss << std::setprecision(precision);
36 if (scientific)
37 ss << std::scientific;
38 ss << v;
39 return ss.str();
40 }
41
42}
43
44namespace Eigen
45{
46 /**
47 * @brief `Eigen::Matrix` wrapper that hides `begin`/`end` from fmt.
48 *
49 * @details When both `fmt/ranges.h` and Eigen are present, the fmt range
50 * formatter picks up `Eigen::Matrix` via its iterator interface and renders
51 * it as a bracketed list (`[1, 2, 3]`). This disables Eigen's stream
52 * formatting path. This wrapper inherits from `Eigen::Matrix` but deletes
53 * `begin` / `end`, so fmt falls back to the ostream formatter.
54 *
55 * Use this type (or its @ref VectorFMTSafe / @ref RowVectorFMTSafe aliases) wherever
56 * Eigen objects need to pass through `fmt::format`.
57 */
58 // NOLINTBEGIN(bugprone-branch-clone): both non-row-vector arms of the
59 // options ternary intentionally select ColMajor. Keeping the two arms
60 // explicit documents the intent at the declaration site.
61 template <class T, int M, int N, int options = AutoAlign | ((M == 1 && N != 1) ? Eigen ::RowMajor : M != 1 || N == 1 ? Eigen ::ColMajor
62 : Eigen ::ColMajor),
63 int max_m = M, int max_n = N>
64 struct MatrixFMTSafe : public Matrix<T, M, N, options, max_m, max_n>
65 // NOLINTEND(bugprone-branch-clone)
66 {
67 using Base = Matrix<T, M, N, options, max_m, max_n>;
68 using Base::Base;
69
70 /// @brief Deleted to hide range interface from fmt.
71 void begin() = delete;
72 /// @brief Deleted to hide range interface from fmt.
73 void end() = delete;
74 };
75
76 /// @brief Column-vector alias of @ref DNDS::MatrixFMTSafe "MatrixFMTSafe".
77 template <class T, int M>
79
80 /// @brief Row-vector alias of @ref DNDS::MatrixFMTSafe "MatrixFMTSafe".
81 template <class T, int N>
83}
84
85namespace DNDS::Meta
86{
87 /// @brief Type trait: is `T` a @ref DNDS::MatrixFMTSafe "MatrixFMTSafe" with real scalar? Used by
88 /// the fmt formatter below to catch both wrapped and unwrapped Eigen types.
89 template <class T>
90 struct is_real_eigen_fmt_safe_matrix : public std::false_type
91 {
92 };
93
94 template <int M, int N, int options, int max_m, int max_n>
98
99 template <class T>
101
102 const bool v = is_real_eigen_fmt_safe_matrix_v<Eigen::MatrixFMTSafe<real, 3, 3>>;
103}
104
105// formatter support for dense eigen matrices
106// ! is not compatible with fmt/ranges.h
107// ! use Eigen::MatrixFMTSafe if fmt/ranges.h is present
108// ! Eigen::Vector s would be fine if fmt/ranges.h is present, but using fmt/ranges.h syntax
109template <typename T, typename Char>
110struct fmt::formatter<T, Char,
111 std::enable_if_t<DNDS::Meta::is_eigen_dense_v<std::remove_cv_t<T>> ||
112 DNDS::Meta::is_real_eigen_fmt_safe_matrix_v<std::remove_cv_t<T>>>>
113// template <int M, int N, int options, int max_m, int max_n, class Char>
114// struct fmt::formatter<Eigen::Matrix<DNDS::real, M, N, options, max_m, max_n>, Char>
115{
116 // using TMat = Eigen::Matrix<DNDS::real, M, N, options, max_m, max_n>;
117 using TMat = std::remove_cv_t<T>;
118 char align = '>';
119 char sign = ' ';
120 int width = -1;
121 int precision = 16;
122 char type = 'g';
123 std::string formatSpecC = "{}";
124
125 auto parse(fmt::format_parse_context &ctx)
126 {
127 auto it = ctx.begin(), end = ctx.end();
128 bool afterDot = false;
129 while (it != end && *it != '}')
130 { // a home-cooked version of float point format parser
131 switch (*it)
132 {
133 case '<':
134 case '>':
135 case '^':
136 align = *it++;
137 break;
138 case '+':
139 case '-':
140 case ' ':
141 sign = *it++;
142 break;
143 case 'e':
144 case 'E':
145 case 'f':
146 case 'F':
147 case 'g':
148 case 'G':
149 type = *it++;
150 break;
151 case '.':
152 afterDot = true;
153 it++;
154 break;
155 default:
156 {
157 if (*it >= '0' && *it <= '9')
158 {
159 std::string v;
160 v.reserve(20);
161 while (it != end && *it >= '0' && *it <= '9')
162 v.push_back(*it++);
163 if (afterDot)
164 precision = std::stoi(v);
165 else
166 width = std::stoi(v);
167 }
168 else
169 DNDS_assert_info(false, fmt::format("invalid char {}", *it));
170 }
171 break;
172 }
173 }
174 if (width == -1)
175 formatSpecC = fmt::format(FMT_STRING("{{:{0}{1}.{3}{4}}}"), align, sign, width, precision, type);
176 else
177 formatSpecC = fmt::format(FMT_STRING("{{:{0}{1}{2}.{3}{4}}}"), align, sign, width, precision, type);
178 return it;
179 }
180
181 auto format(const TMat &mat, fmt::format_context &ctx) const
182 {
183 std::string buf;
184 buf.reserve(mat.size() * 10);
185 buf.push_back('[');
186 for (Eigen::Index i = 0; i < mat.rows(); ++i)
187 {
188 for (Eigen::Index j = 0; j < mat.cols(); ++j)
189 {
190 if (j > 0)
191 buf.append(",");
192 fmt::format_to(std::back_inserter(buf), formatSpecC, mat(i, j));
193 }
194 if (i < mat.rows() - 1)
195 buf.append(";\n");
196 }
197 buf.push_back(']');
198 return fmt::format_to(ctx.out(), "{}", buf);
199 }
200};
201
202namespace DNDS
203{
204
205 template <DeviceBackend B, typename t_scalar, int M, int N>
207 {
208 public:
209 static_assert(M >= 0 || M == Eigen::Dynamic, "M needs to be a valid eigen size");
210 static_assert(N >= 0 || N == Eigen::Dynamic, "N needs to be a valid eigen size");
211 using t_matrix = Eigen::Matrix<std::remove_cv_t<t_scalar>, M, N>;
212 using t_map_const = Eigen::Map<const t_matrix>;
213 using t_map = std::conditional_t<std::is_const_v<t_scalar>,
214 t_map_const, Eigen::Map<t_matrix>>;
216
217 protected:
218 t_scalar *ptr = nullptr;
219 std::conditional_t<M >= 0, EmptyNoDefault, rowsize> M_dynamic = 0;
220 std::conditional_t<N >= 0, EmptyNoDefault, rowsize> N_dynamic = 0;
221
222 public:
224
226 : ptr(n_ptr), M_dynamic(m), N_dynamic(n)
227 {
228 }
229
230 DNDS_DEVICE_CALLABLE [[nodiscard]] t_scalar *data() const
231 {
232 return ptr;
233 }
234
235 DNDS_DEVICE_CALLABLE [[nodiscard]] rowsize rows() const
236 {
237 if constexpr (M >= 0)
238 return M;
239 else
240 return M_dynamic;
241 }
242
243 DNDS_DEVICE_CALLABLE [[nodiscard]] rowsize cols() const
244 {
245 if constexpr (N >= 0)
246 return N;
247 else
248 return N_dynamic;
249 }
250
252 {
253 return {ptr, rows(), cols()};
254 }
255
257 {
258 return {ptr, rows(), cols()};
259 }
260 };
261
262 static_assert(std::is_trivially_copyable_v<EigenMatrixView<DeviceBackend::Host, real, Eigen::Dynamic, Eigen::Dynamic>>);
263
264 template <typename t_scalar, int M, int N>
266 {
267 public:
268 using t_matrix = Eigen::Matrix<t_scalar, M, N>;
269 using t_map = Eigen::Map<t_matrix>;
270
271 protected:
275
276 public:
278 {
279 h_data.resize(this->size());
280 }
281
283 {
284 if constexpr (M >= 0)
285 return M;
286 else
287 return M_dynamic;
288 }
289
291 {
292 if constexpr (N >= 0)
293 return N;
294 else
295 return N_dynamic;
296 }
297
299 {
300 return rows() * cols();
301 }
302
303 void resize(int m, int n)
304 {
305 if constexpr (M >= 0)
306 DNDS_assert(M == m);
307 else
308 M_dynamic = m;
309 if constexpr (N >= 0)
310 DNDS_assert(N == n);
311 else
312 N_dynamic = n;
313 h_data.resize(this->size());
314 }
315
317 {
318 return {h_data.data(), this->rows(), this->cols()};
319 }
320
321 template <DeviceBackend B>
323
324 void to_host()
325 {
326 h_data.to_host();
327 }
328
330 {
331 h_data.to_device(B);
332 }
333
334 template <DeviceBackend B>
336 {
337 DNDS_assert_info(h_data.device() == B || (h_data.device() == DeviceBackend::Unknown && B == DeviceBackend::Host),
338 "data not on this backend");
339 switch (B)
340 {
343 return t_deviceView<B>{h_data.data(), rows(), cols()};
344#ifdef DNDS_USE_CUDA
345 case DeviceBackend::CUDA:
346 return t_deviceView<B>{h_data.dataDevice(), rows(), cols()};
347#endif
348 default:
349 DNDS_assert_info(false, std::string("this device not implemented -- ") + device_backend_name(B));
350 return t_deviceView<B>{h_data.dataDevice(), rows(), cols()};
351 }
352 }
353 };
354}
Core type aliases, constants, and metaprogramming utilities for the DNDS framework.
#define DNDS_DEVICE_TRIVIAL_COPY_DEFINE(T, T_Self)
Definition Defines.hpp:87
#define DNDS_DEVICE_CALLABLE
Definition Defines.hpp:76
Device memory abstraction layer with backend-specific storage and factory creation.
Pre-compiled-header style shim that includes the heavy Eigen headers under DNDSR's warning suppressio...
#define DNDS_assert_info(expr, info)
Debug-only assertion with an extra std::string info message.
Definition Errors.hpp:117
#define DNDS_assert(expr)
Debug-only assertion (compiled out when DNDS_NDEBUG is defined). Prints the expression + file/line + ...
Definition Errors.hpp:112
Eigen::Matrix< real, 3, 3 > m
Host-device vector types with optional GPU storage and device-side views.
std::conditional_t< std::is_const_v< t_scalar >, t_map_const, Eigen::Map< t_matrix > > t_map
DNDS_DEVICE_CALLABLE rowsize cols() const
DNDS_DEVICE_CALLABLE rowsize rows() const
DNDS_DEVICE_CALLABLE t_map_const map() const
Eigen::Map< const t_matrix > t_map_const
DNDS_DEVICE_CALLABLE t_scalar * data() const
Eigen::Matrix< std::remove_cv_t< t_scalar >, M, N > t_matrix
DNDS_DEVICE_CALLABLE t_map map()
void resize(int m, int n)
t_deviceView< B > deviceView()
Eigen::Map< t_matrix > t_map
void to_device(DeviceBackend B)
host_device_vector< t_scalar > h_data
Eigen::Matrix< t_scalar, M, N > t_matrix
EigenMatrixView< B, t_scalar, M, N > t_deviceView
constexpr bool is_real_eigen_fmt_safe_matrix_v
the host side operators are provided as implemented
DeviceBackend
Enumerates the backends a DeviceStorage / Array can live on.
@ Unknown
Unset / sentinel.
@ Host
Plain CPU memory.
const char * device_backend_name(DeviceBackend B)
Canonical string name for a DeviceBackend (used in log messages).
int32_t rowsize
Row-width / per-row element-count type (signed 32-bit).
Definition Defines.hpp:114
std::string to_string(const Eigen::DenseBase< dir > &v, int precision=5, bool scientific=false)
Render an Eigen::DenseBase to a string via operator<<.
Definition EigenUtil.hpp:29
Type trait: is T a MatrixFMTSafe with real scalar? Used by the fmt formatter below to catch both wrap...
Definition EigenUtil.hpp:91
Eigen::Matrix wrapper that hides begin/end from fmt.
Definition EigenUtil.hpp:66
void begin()=delete
Deleted to hide range interface from fmt.
Matrix< T, M, N, options, max_m, max_n > Base
Definition EigenUtil.hpp:67
void end()=delete
Deleted to hide range interface from fmt.
Eigen::Matrix< real, 5, 1 > v
constexpr DNDS::index N
Eigen::Vector3d n(1.0, 0.0, 0.0)