EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Print.h
Go to the documentation of this file.
1#pragma once
2
23
25
26#ifndef EMRYS_FILTER_NO_IO
27
28# include <expected>
29# include <format>
30# include <ostream>
31# include <sstream>
32# include <string>
33# include <string_view>
34
36
37namespace Emrys
38{
39
40namespace internal
41{
42
47struct Indent
48{
49 int depth = 0;
50
52 [[nodiscard]] Indent
53 deeper() const
54 {
55 return {depth + 1};
56 }
57};
58
60inline std::ostream &
61operator<<(std::ostream & stream, Indent indent)
62{
63 for (int i = 0; i < indent.depth; ++i)
64 {
65 stream << " ";
66 }
67 return stream;
68}
69
74template <typename T>
75concept Printable = requires(std::ostream & stream, T const & value) { describe(stream, value, Indent{}); };
76
80template <typename Derived>
81void
82printRow(std::ostream & stream, Indent indent, std::string_view label, Eigen::DenseBase<Derived> const & vector)
83{
84 static auto const row = Eigen::IOFormat{Eigen::StreamPrecision, Eigen::DontAlignCols, " ", " "};
85 stream << indent << label << ':';
86 if (vector.size() > 0)
87 {
88 stream << ' ' << vector.reshaped().transpose().format(row);
89 }
90}
91
95template <typename Derived>
96void
97printMatrix(std::ostream & stream, Indent indent, std::string_view label, Eigen::DenseBase<Derived> const & matrix)
98{
99 stream << indent << label << ':';
100 if (matrix.size() > 0)
101 {
102 auto prefix = std::ostringstream{};
103 prefix << indent.deeper();
104 auto const rows = Eigen::IOFormat{Eigen::StreamPrecision, 0, " ", "\n", prefix.str()};
105 stream << '\n' << matrix.format(rows);
106 }
107}
108
110inline void
111printDimension(std::ostream & stream, Eigen::Index valueDimension, Eigen::Index differenceDimension)
112{
113 stream << " of dimension " << valueDimension;
115 {
116 stream << " (" << differenceDimension << " in differences)";
117 }
118}
119
121inline void
122printCount(std::ostream & stream, Eigen::Index count, std::string_view noun)
123{
124 stream << count << ' ' << noun << (count == 1 ? "" : "s");
125}
126
130template <typename M>
131void
132printMoments(std::ostream & stream, Indent indent, M const & moments)
133{
134 stream << '\n';
135 printRow(stream, indent, "mean", moments.mean());
136 stream << '\n';
137 printMatrix(stream, indent, "covariance", moments.covarianceLower());
138}
139
143template <typename M, typename E>
144void
145printMoments(std::ostream & stream, Indent indent, std::expected<M, E> const & moments)
146{
147 if (moments)
148 {
149 printMoments(stream, indent, *moments);
150 }
151 else
152 {
153 stream << '\n' << indent << "no moments: ";
154 describe(stream, moments.error(), Indent{});
155 }
156}
157
161template <typename State>
162void
163describeFilter(std::ostream & stream, std::string_view name, State const & state, Indent indent)
164{
165 stream << indent << name << '\n';
166 describe(stream, state, indent.deeper());
167}
168
170inline void
171describe(std::ostream & stream, NumericError error, Indent indent)
172{
173 stream << indent;
174 switch (error)
175 {
177 stream << "the covariance is not positive definite";
178 break;
180 stream << "the precision is singular";
181 break;
183 stream << "a value is not finite";
184 break;
186 stream << "no weights";
187 break;
189 stream << "every weight is zero";
190 break;
191 }
192}
193
194} // namespace internal
195
205template <internal::Printable T>
206std::ostream &
207operator<<(std::ostream & stream, T const & value)
208{
209 describe(stream, value, internal::Indent{});
210 return stream;
211}
212
213} // namespace Emrys
214
230template <Emrys::internal::Printable T>
231struct std::formatter<T, char> : std::formatter<std::string_view, char>
232{
236 template <typename ParseContext>
237 constexpr typename ParseContext::iterator
238 parse(ParseContext & context)
239 {
240 auto const begin = context.begin();
241 auto end = begin;
242 while (end != context.end() && *end != '}')
243 {
244 ++end;
245 }
246 auto const spec = std::string_view{begin, end};
247 // The spec without a trailing type s, which is a string's default.
248 auto const body = spec.ends_with('s') ? spec.substr(0, spec.size() - 1) : spec;
249 // A precision is a '.' and the digits after it, at the end. A '.' as the fill is followed
250 // by an alignment, so a '.' just before the last digits is always the precision.
251 auto digits = body.size();
252 while (digits > 0 && body[digits - 1] >= '0' && body[digits - 1] <= '9')
253 {
254 --digits;
255 }
256 if (digits == body.size() || digits == 0 || body[digits - 1] != '.')
257 {
258 return std::formatter<std::string_view, char>::parse(context);
259 }
260 int precision = 0;
261 for (char const digit : body.substr(digits))
262 {
263 precision = precision * 10 + (digit - '0');
264 }
265 m_precision = precision;
266 // The string's part, the spec before the precision, parsed on its own.
267 auto rest = std::format_parse_context{body.substr(0, digits - 1)};
268 std::formatter<std::string_view, char>::parse(rest);
269 return end;
270 }
271
275 template <typename Context>
276 [[nodiscard]] typename Context::iterator
277 format(T const & value, Context & context) const
278 {
279 auto stream = std::ostringstream{};
280 if (m_precision >= 0)
281 {
282 stream.precision(m_precision);
283 }
284 stream << value;
285 return std::formatter<std::string_view, char>::format(stream.str(), context);
286 }
287
288private:
289
290 int m_precision = -1;
291};
292
293#endif // EMRYS_FILTER_NO_IO
The macros every header includes first.
Log-weights: the WeightError a normalization can fail with (NumericError.h), and the streaming log-su...
std::ostream & operator<<(std::ostream &stream, T const &value)
Prints value, any type the library describes (internal::Printable): a distribution,...
Definition Print.h:207
NumericError
Reasons a computation from parameters the caller gave can fail: a matrix that does not factorize or a...
Definition NumericError.h:30
@ NotPositiveDefinite
the covariance is not positive definite, so it has no Cholesky factor or inverse
Definition NumericError.h:31
@ NonFinite
a matrix, a factor, or a log-weight holds NaN or infinity (+inf, for a log-weight)
Definition NumericError.h:33
@ ZeroSum
every weight is zero (every log-weight is -inf)
Definition NumericError.h:35
@ Empty
there are no weights
Definition NumericError.h:34
@ Singular
the precision is not positive definite, so the mean and covariance are undefined
Definition NumericError.h:32
Eigen::Index valueDimension(S const &space)
Definition Space.h:272
Eigen::Index differenceDimension(S const &space)
Definition Space.h:284
Context::iterator format(T const &value, Context &context) const
Writes the description of value to context's output, its numbers at the precision given,...
Definition Print.h:277
constexpr ParseContext::iterator parse(ParseContext &context)
Parses context's specification: a precision at its end is the stream's, and the rest is the string's.
Definition Print.h:238