22
33#include < cmath>
44#include < format>
5+ #include < limits>
6+ #include < memory>
57#include < vector>
68
79#include < GridKit/Testing/OutputAtTime.hpp>
@@ -11,96 +13,228 @@ namespace GridKit
1113 namespace Testing
1214 {
1315
16+ inline constexpr double DEFAULT_ABS_ERROR_THRESHOLD =
17+ std::numeric_limits<double >::epsilon();
18+
1419 /* *
15- * @brief Aggregate error data for a single variable
20+ * @brief Aggregate norm data for a single variable over time
1621 */
17- struct ErrorAggregate
22+ struct TemporalNormAggregate
1823 {
24+ // / Name of variable
1925 std::string label;
20- double max_error{0.0 };
21- double max_error_time{0.0 };
22- double error_norm_L2{0.0 };
26+ // / L-inf norm
27+ double max_value{0.0 };
28+ // / Time at which max value occurred
29+ double max_value_time{0.0 };
30+ // / L2 norm
31+ double L2 {0.0 };
2332
24- void push (double err, double time)
33+ /* *
34+ * @brief Add a new value for the variable to be aggregated
35+ */
36+ void push (double val, double time)
2537 {
26- if (err > max_error )
38+ if (val > max_value )
2739 {
28- max_error = err ;
29- max_error_time = time;
40+ max_value = val ;
41+ max_value_time = time;
3042 }
31- error_norm_L2 += err * err ;
43+ L2 += val * val ;
3244 }
3345
46+ /* *
47+ * @brief Finalize the calculation(s)
48+ */
3449 void wrap ()
3550 {
36- error_norm_L2 = std::sqrt (error_norm_L2);
51+ L2 = std::sqrt (L2 );
52+ }
53+
54+ /* *
55+ * @brief Scale by a reference value (only if the current value is above
56+ * the given threshold (used internally for relative errors)
57+ */
58+ void scale (const TemporalNormAggregate& ref, double threshold)
59+ {
60+ if (ref.max_value > threshold)
61+ {
62+ max_value /= ref.max_value ;
63+ }
64+ if (ref.L2 > threshold)
65+ {
66+ L2 /= ref.L2 ;
67+ }
3768 }
3869
70+ /* *
71+ * @brief Pretty-print label and values to output stream
72+ */
3973 std::ostream& display (
4074 std::ostream& os = std::cout, const std::string& indent = " " ) const
4175 {
4276 os << indent << label << " :\n "
4377 << indent << indent << " max : "
44- << std::format (" {:.6e} (at time {:.3e})" , max_error, max_error_time )
78+ << std::format (" {:.6e} (at time {:.3e})" , max_value, max_value_time )
4579 << ' \n '
4680 << indent << indent << " L2-norm : "
47- << std::format (" {:.6e}" , error_norm_L2 ) << ' \n ' ;
81+ << std::format (" {:.6e}" , L2 ) << ' \n ' ;
4882 return os;
4983 }
5084 };
5185
5286 /* *
53- * @brief A set of ErrorAggregate for each variable plus a total (combined )
54- * ErrorAggregate
87+ * @brief A set of aggregate norms (represented with TemporalNormAggregate )
88+ * for the error in each variable plus one for the total (combined) error
5589 *
56- * @note The "total " aggregate is based on the L-infinity norm of the local
57- * error of variables at a given time step.
90+ * @note The "total_error " aggregate is based on the L-infinity norm of the
91+ * local error of variables at a given time step.
5892 */
5993 struct ErrorSet
6094 {
61- ErrorAggregate total{" Total" };
62- std::vector<ErrorAggregate> vars{};
95+ // / Aggregate of the combined error value at each time step
96+ TemporalNormAggregate total_error{" Total" };
97+ // / Aggregate error for each variable
98+ std::vector<TemporalNormAggregate> var_errors{};
6399
100+ /* *
101+ * @brief Construct with variable labels
102+ */
64103 template <typename C>
65104 explicit ErrorSet (const C& labels)
66- : vars (std::size(labels))
105+ : var_errors (std::size(labels))
67106 {
68107 for (std::size_t i = 0 ; i < std::size (labels); ++i)
69108 {
70- vars [i].label = labels[i];
109+ var_errors [i].label = labels[i];
71110 }
72111 }
73112
74- void push ( const OutputAtTime& err )
113+ virtual ~ErrorSet ( )
75114 {
76- for (std::size_t i = 0 ; i < vars.size (); ++i)
77- {
78- vars[i].push (std::abs (err.data [i]), err.t );
79- }
80- total.push (lInfNorm (err), err.t );
81115 }
82116
83- void wrap ()
117+ /* *
118+ * @brief Finalize the calculations
119+ */
120+ virtual void wrap ()
84121 {
85- for (auto & agg : vars )
122+ for (auto & agg : var_errors )
86123 {
87124 agg.wrap ();
88125 }
89- total .wrap ();
126+ total_error .wrap ();
90127 }
91128
129+ /* *
130+ * @brief Take the error between the two output parameters for each
131+ * variable and add to the aggregate
132+ */
133+ virtual void push (const OutputAtTime&, const OutputAtTime&) = 0;
134+
135+ /* *
136+ * @brief Pretty-print the set of errors for each variable and total
137+ */
92138 std::ostream& display (std::ostream& os = std::cout) const
93139 {
94140 std::string indent{" " };
95141 os << " Error Set:\n " ;
96- for (const auto & var : vars )
142+ for (const auto & var : var_errors )
97143 {
98144 var.display (os, indent);
99145 }
100- total .display (os, indent);
146+ total_error .display (os, indent);
101147 return os;
102148 }
103149 };
104150
151+ enum class ErrorType
152+ {
153+ RELATIVE ,
154+ ABSOLUTE
155+ };
156+
157+ struct RelativeError ;
158+ struct AbsoluteError ;
159+
160+ template <typename error_type = RelativeError>
161+ struct ErrorSetImpl ;
162+
163+ template <>
164+ struct ErrorSetImpl <RelativeError> : ErrorSet
165+ {
166+ template <typename C>
167+ explicit ErrorSetImpl (
168+ const C& labels,
169+ double abs_threshold = DEFAULT_ABS_ERROR_THRESHOLD )
170+ : ErrorSet(labels),
171+ abs_threshold_(abs_threshold),
172+ ref_norms_(std::size(labels))
173+ {
174+ }
175+
176+ void push (const OutputAtTime& test, const OutputAtTime& ref) override
177+ {
178+ auto err = test - ref;
179+ for (std::size_t i = 0 ; i < var_errors.size (); ++i)
180+ {
181+ var_errors[i].push (std::abs (err.data [i]), err.t );
182+ ref_norms_[i].push (std::abs (ref.data [i]), ref.t );
183+ }
184+ total_error.push (lInfNorm (err), err.t );
185+ ref_total_norm_.push (lInfNorm (ref), ref.t );
186+ }
187+
188+ void wrap () override
189+ {
190+ ErrorSet::wrap ();
191+ for (std::size_t i = 0 ; i < var_errors.size (); ++i)
192+ {
193+ ref_norms_[i].wrap ();
194+ var_errors[i].scale (ref_norms_[i], abs_threshold_);
195+ }
196+ ref_total_norm_.wrap ();
197+ total_error.scale (ref_total_norm_, abs_threshold_);
198+ }
199+
200+ private:
201+ double abs_threshold_{};
202+ TemporalNormAggregate ref_total_norm_{};
203+ std::vector<TemporalNormAggregate> ref_norms_{};
204+ };
205+
206+ template <>
207+ struct ErrorSetImpl <AbsoluteError> : ErrorSet
208+ {
209+ using ErrorSet::ErrorSet;
210+
211+ void push (const OutputAtTime& test, const OutputAtTime& ref) override
212+ {
213+ auto err = test - ref;
214+ for (std::size_t i = 0 ; i < var_errors.size (); ++i)
215+ {
216+ var_errors[i].push (std::abs (err.data [i]), err.t );
217+ }
218+ total_error.push (lInfNorm (err), err.t );
219+ }
220+ };
221+
222+ template <typename C>
223+ std::unique_ptr<ErrorSet> makeErrorSet (
224+ ErrorType type,
225+ const C& labels,
226+ double abs_threshold = DEFAULT_ABS_ERROR_THRESHOLD )
227+ {
228+ switch (type)
229+ {
230+ case ErrorType::RELATIVE :
231+ return std::make_unique<ErrorSetImpl<RelativeError>>(labels, abs_threshold);
232+ case ErrorType::ABSOLUTE :
233+ return std::make_unique<ErrorSetImpl<AbsoluteError>>(labels);
234+ default :
235+ throw std::runtime_error (" Invalid error type" );
236+ }
237+ }
238+
105239 } // namespace Testing
106240} // namespace GridKit
0 commit comments