Line data Source code
1 : /// \file matrix_io.h
2 : /// \brief Stream output operators for all matrix types in the trackingLib math library.
3 : ///
4 : /// This file provides template-based operator<< implementations for streaming matrix objects
5 : /// to any std::ostream (cout, files, stringstream). The design uses SFINAE-based type detection
6 : /// to work with all matrix-like types that provide the required interface.
7 : ///
8 : /// Key features:
9 : /// - Works with any std::ostream (cout, files, stringstream)
10 : /// - Template-based implementation for compile-time type safety
11 : /// - SFINAE-based type detection for C++17 compatibility
12 : /// - Specialized formatting for DiagonalMatrix, TriangularMatrix, and Vector
13 : /// - Future-proof design using at_unsafe() interface
14 : ///
15 : /// \note This file replaces the previous print() methods across all matrix types,
16 : /// providing idiomatic C++ stream output without code duplication.
17 : ///
18 : /// \see matrix.h for the base Matrix class
19 : /// \see diagonal_matrix.h for DiagonalMatrix specialization
20 : /// \see vector.h for Vector specialization
21 :
22 : #ifndef C63CC10A_92E8_4A79_8412_56D9CFCB8DB4
23 : #define C63CC10A_92E8_4A79_8412_56D9CFCB8DB4
24 :
25 : #include "base/first_include.h" // IWYU pragma: keep
26 : #include "math/linalg/triangular_matrix.h"
27 : #include <iomanip>
28 : #include <ostream>
29 : #include <type_traits>
30 :
31 : namespace tracking
32 : {
33 : namespace math
34 : {
35 :
36 : // Forward declarations
37 : template <typename ValueType_, sint32 Size_>
38 : class DiagonalMatrix;
39 :
40 : template <typename ValueType_, sint32 Size_>
41 : class Vector;
42 :
43 : /// \brief SFINAE helper to detect matrix-like types at compile time.
44 : ///
45 : /// This trait uses SFINAE (Substitution Failure is Not An Error) to detect whether
46 : /// a type provides the interface expected of matrix-like objects. It checks for:
47 : /// - An at_unsafe(sint32, sint32) method for element access
48 : /// - A nested value_type typedef
49 : /// - Static Rows and Cols constants
50 : ///
51 : /// This allows the operator<< template to work with any matrix type that provides
52 : /// the required interface, enabling compile-time polymorphism without inheritance.
53 : ///
54 : /// \tparam T The type to check for matrix-like properties
55 : /// \tparam Enable SFINAE parameter (automatically deduced)
56 : ///
57 : /// \see operator<<(std::ostream&, const M&) for usage
58 : template <typename T, typename = void>
59 : struct is_matrix_like: std::false_type
60 : {
61 : };
62 :
63 : template <typename T>
64 : struct is_matrix_like<T,
65 : std::void_t<decltype(std::declval<const T&>().at_unsafe(sint32{}, sint32{})),
66 : typename T::value_type,
67 : decltype(T::Rows),
68 : decltype(T::Cols)>>: std::true_type
69 : {
70 : };
71 :
72 : /// \brief Convenience variable template for is_matrix_like trait.
73 : ///
74 : /// Provides a more convenient syntax for checking if a type is matrix-like.
75 : /// Equivalent to is_matrix_like<T>::value.
76 : ///
77 : /// \tparam T The type to check
78 : ///
79 : /// \see is_matrix_like
80 : template <typename T>
81 : inline constexpr bool is_matrix_like_v = is_matrix_like<T>::value;
82 :
83 : /// \brief Template operator<< for streaming matrix-like objects to output streams.
84 : ///
85 : /// This function provides idiomatic C++ stream output for all matrix types in the trackingLib
86 : /// math library. It uses SFINAE to enable the operator only for types that provide the
87 : /// required matrix interface.
88 : ///
89 : /// The output format provides:
90 : /// - Fixed-width columns for alignment
91 : /// - 6 decimal places for floating-point types with sign display
92 : /// - Comma-separated values within rows
93 : /// - One row per line
94 : ///
95 : /// \tparam M The matrix type (automatically deduced)
96 : /// \param[in,out] os The output stream to write to
97 : /// \param[in] matrix The matrix object to stream
98 : /// \return Reference to the output stream for chaining
99 : ///
100 : /// \note Uses at_unsafe() for element access, which is future-proof for packed storage
101 : /// \note Formatting is optimized for readability in debug/console output
102 : ///
103 : /// \see operator<<(std::ostream&, const DiagonalMatrix&) for DiagonalMatrix specialization
104 : ///
105 : /// Usage example:
106 : /// \code{.cpp}
107 : /// Matrix<float32, 2, 3> mat = /* ... */;
108 : /// std::cout << mat << std::endl;
109 : /// // Output:
110 : /// // +1.000000, +2.000000, +3.000000
111 : /// // +4.000000, +5.000000, +6.000000
112 : /// \endcode
113 : template <typename M>
114 19 : auto operator<<(std::ostream& os, const M& matrix) -> std::enable_if_t<is_matrix_like_v<M>, std::ostream&>
115 : {
116 : using value_type = typename M::value_type;
117 :
118 : // Access elements via at_unsafe() - works regardless of internal memory layout
119 : // This is future-proof for packed triangular matrixes
120 83 : for (sint32 row = 0; row < M::Rows; ++row)
121 : {
122 280 : for (sint32 col = 0; col < M::Cols; ++col)
123 : {
124 : if constexpr (std::is_floating_point_v<value_type>)
125 : {
126 208 : os << std::fixed << std::setprecision(10) << std::showpos << std::setw(16) << matrix.at_unsafe(row, col);
127 : }
128 : else
129 : {
130 8 : os << std::setw(8) << matrix.at_unsafe(row, col);
131 : }
132 :
133 216 : if (col < M::Cols - 1)
134 : {
135 152 : os << ", ";
136 : }
137 : }
138 64 : os << "\n";
139 : }
140 19 : return os;
141 : }
142 :
143 : /// \brief Specialization of operator<< for TriangularMatrix.
144 : ///
145 : /// TriangularMatrix requires special handling because it doesn't allow to access
146 : /// off-diagonal matrix elements
147 : /// This specialization outputs the full matrix representation with zeros off-diagonal.
148 : ///
149 : /// The output format matches the general matrix operator<< for consistency:
150 : /// - Fixed-width columns for alignment
151 : /// - 6 decimal places for floating-point types with sign display
152 : /// - Triangular elements from stored values, off-diagonal as zero
153 : ///
154 : /// \tparam ValueType_ The atomic data type of internal elements
155 : /// \tparam Size_ The matrix dimension (compile-time constant)
156 : /// \tparam IsLower_ Whether the matrix is lower triangular
157 : /// \tparam IsRowMajor_ Whether the matrix is stored in row-major order
158 : /// \param[in,out] os The output stream to write to
159 : /// \param[in] matrix The TriangularMatrix object to stream
160 : /// \return Reference to the output stream for chaining
161 : ///
162 : /// \note This specialization is necessary because TriangularMatrix::at_unsafe() has
163 : /// restricted access to only triangular elements
164 : ///
165 : /// \see TriangularMatrix for the matrix class
166 : /// \see operator<<(std::ostream&, const M&) for general matrix streaming
167 : template <typename ValueType_, sint32 Size_, bool IsLower_, bool IsRowMajor_>
168 1 : std::ostream& operator<<(std::ostream& os,
169 : const tracking::math::TriangularMatrix<ValueType_, Size_, IsLower_, IsRowMajor_>& matrix)
170 : {
171 4 : for (sint32 row = 0; row < Size_; ++row)
172 : {
173 12 : for (sint32 col = 0; col < Size_; ++col)
174 : {
175 : if constexpr (std::is_floating_point_v<ValueType_>)
176 : {
177 9 : os << std::fixed << std::setprecision(10) << std::showpos << std::setw(16);
178 : }
179 : else
180 : {
181 : os << std::setw(8);
182 : }
183 :
184 : // For triangular matrix, only triangular elements are stored
185 9 : if ((IsLower_ && row >= col) || (!IsLower_ && row <= col))
186 : {
187 6 : os << matrix.at_unsafe(row, col);
188 : }
189 : else
190 : {
191 3 : os << static_cast<ValueType_>(0);
192 : }
193 :
194 9 : if (col < Size_ - 1)
195 : {
196 6 : os << ", ";
197 : }
198 : }
199 3 : os << "\n";
200 : }
201 1 : return os;
202 : }
203 :
204 : /// \brief Specialization of operator<< for DiagonalMatrix.
205 : ///
206 : /// DiagonalMatrix requires special handling because it stores only diagonal elements
207 : /// and has a different at_unsafe() signature (single index instead of row,col).
208 : /// This specialization outputs the full matrix representation with zeros off-diagonal.
209 : ///
210 : /// The output format matches the general matrix operator<< for consistency:
211 : /// - Fixed-width columns for alignment
212 : /// - 6 decimal places for floating-point types with sign display
213 : /// - Diagonal elements from stored values, off-diagonal as zero
214 : ///
215 : /// \tparam value_type The element type (float32, float64, etc.)
216 : /// \tparam Size The matrix dimension (compile-time constant)
217 : /// \param[in,out] os The output stream to write to
218 : /// \param[in] matrix The DiagonalMatrix object to stream
219 : /// \return Reference to the output stream for chaining
220 : ///
221 : /// \note This specialization is necessary because DiagonalMatrix::at_unsafe() takes
222 : /// only one parameter (diagonal index) unlike general matrixes
223 : ///
224 : /// \see DiagonalMatrix for the matrix class
225 : /// \see operator<<(std::ostream&, const M&) for general matrix streaming
226 : ///
227 : /// Usage example:
228 : /// \code{.cpp}
229 : /// DiagonalMatrix<float32, 3> diag = /* diagonal [1, 2, 3] */;
230 : /// std::cout << diag << std::endl;
231 : /// // Output:
232 : /// // +1.000000, +0.000000, +0.000000
233 : /// // +0.000000, +2.000000, +0.000000
234 : /// // +0.000000, +0.000000, +3.000000
235 : /// \endcode
236 : template <typename ValueType_, sint32 Size_>
237 1 : std::ostream& operator<<(std::ostream& os, const tracking::math::DiagonalMatrix<ValueType_, Size_>& matrix)
238 : {
239 4 : for (sint32 row = 0; row < Size_; ++row)
240 : {
241 12 : for (sint32 col = 0; col < Size_; ++col)
242 : {
243 : if constexpr (std::is_floating_point_v<ValueType_>)
244 : {
245 9 : os << std::fixed << std::setprecision(10) << std::showpos << std::setw(16);
246 : }
247 : else
248 : {
249 : os << std::setw(8);
250 : }
251 :
252 : // For diagonal matrix, only diagonal elements are stored
253 9 : if (row == col)
254 : {
255 3 : os << matrix.at_unsafe(row);
256 : }
257 : else
258 : {
259 6 : os << static_cast<ValueType_>(0);
260 : }
261 :
262 9 : if (col < Size_ - 1)
263 : {
264 6 : os << ", ";
265 : }
266 : }
267 3 : os << "\n";
268 : }
269 1 : return os;
270 : }
271 :
272 : /// \brief Specialization of operator<< for Vector.
273 : ///
274 : /// Vector requires special handling because it has a different at_unsafe() signature
275 : /// (single index instead of row,col) and represents a column vector.
276 : /// This specialization outputs the vector as a column matrix.
277 : ///
278 : /// The output format matches the general matrix operator<< for consistency:
279 : /// - Fixed-width columns for alignment
280 : /// - 6 decimal places for floating-point types with sign display
281 : /// - Vector elements as a single column
282 : ///
283 : /// \tparam ValueType_ The atomic data type of internal elements
284 : /// \tparam Size_ The vector dimension (compile-time constant)
285 : /// \param[in,out] os The output stream to write to
286 : /// \param[in] vector The Vector object to stream
287 : /// \return Reference to the output stream for chaining
288 : ///
289 : /// \note This specialization is necessary because Vector::at_unsafe() takes
290 : /// only one parameter (vector index) unlike general matrixes
291 : ///
292 : /// \see Vector for the vector class
293 : /// \see operator<<(std::ostream&, const M&) for general matrix streaming
294 : ///
295 : /// Usage example:
296 : /// \code{.cpp}
297 : /// Vector<float32, 3> vec = /* [1, 2, 3] */;
298 : /// std::cout << vec << std::endl;
299 : /// // Output:
300 : /// // +1.000000
301 : /// // +2.000000
302 : /// // +3.000000
303 : /// \endcode
304 : template <typename ValueType_, sint32 Size_>
305 2 : std::ostream& operator<<(std::ostream& os, const tracking::math::Vector<ValueType_, Size_>& vector)
306 : {
307 9 : for (sint32 row = 0; row < Size_; ++row)
308 : {
309 : if constexpr (std::is_floating_point_v<ValueType_>)
310 : {
311 3 : os << std::fixed << std::setprecision(10) << std::showpos << std::setw(16) << vector.at_unsafe(row);
312 : }
313 : else
314 : {
315 4 : os << std::setw(8) << vector.at_unsafe(row);
316 : }
317 7 : os << "\n";
318 : }
319 2 : return os;
320 : }
321 :
322 : } // namespace math
323 : } // namespace tracking
324 :
325 : #endif // C63CC10A_92E8_4A79_8412_56D9CFCB8DB4
|