vstat
Loading...
Searching...
No Matches
vstat.hpp
1// SPDX-License-Identifier: MIT
2// SPDX-FileCopyrightText: Copyright 2020-2024 Heal Research
3
4#ifndef VSTAT_HPP
5#define VSTAT_HPP
6
7#include <algorithm>
8#include <functional>
9#include <iterator>
10#include <limits>
11#include <numbers>
12#include <type_traits>
13#include <utility>
14
15#include <eve/module/math.hpp>
16#include <eve/module/special.hpp>
17
18#include "bivariate.hpp"
19#include "compensated_sum.hpp"
20#include "univariate.hpp"
21
22namespace VSTAT_NAMESPACE
23{
24
34enum class nan_policy { propagate, omit };
35
36namespace detail
37{
38// utility method to load data into a wide type
39template<eve::simd_value T, std::random_access_iterator I, typename F>
40 requires std::is_invocable_v<F, std::iter_value_t<I>>
41auto inline load(I iter, F&& func)
42{
43 return [&]<std::size_t... Idx>(std::index_sequence<Idx...>) -> auto
44 { return T {std::forward<F>(func)(*(iter + Idx))...}; }(std::make_index_sequence<T::size()> {});
45}
46
47// binary projection overload: applies func(a, b) element-wise across two iterators
48template<eve::simd_value T, std::random_access_iterator I, std::random_access_iterator J, typename F>
49 requires std::is_invocable_v<F, std::iter_value_t<I>, std::iter_value_t<J>>
50auto inline load(I iter1, J iter2, F&& func)
51{
52 return [&]<std::size_t... Idx>(std::index_sequence<Idx...>) -> auto
53 { return T {std::forward<F>(func)(*(iter1 + Idx), *(iter2 + Idx))...}; }(std::make_index_sequence<T::size()> {});
54}
55
56// utility method to advance a set of iterators
57template<typename Distance, typename... Iters>
58auto inline advance(Distance d, Iters&... iters) -> void
59{
60 (std::advance(iters, d), ...);
61}
62} // namespace detail
63
64namespace concepts
65{
66template<typename T>
67concept arithmetic = std::is_arithmetic_v<T>;
68
69template<typename F, typename... Args>
70concept arithmetic_projection = requires(F&&) {
71 { std::is_invocable_v<F, Args...> };
72 { arithmetic<std::remove_reference_t<std::invoke_result_t<F, Args...>>> };
73};
74} // namespace concepts
75
81namespace univariate
82{
95template<std::floating_point T, stats Stats = stats::variance, std::random_access_iterator I, typename F = std::identity>
96 requires concepts::arithmetic_projection<F, std::iter_value_t<I>>
97inline auto accumulate(I first, I last, F&& f = F {}) noexcept -> univariate_statistics
98{
99 using wide = eve::wide<T>;
100 auto constexpr s {wide::size()};
101 auto const n {std::distance(first, last)};
102 auto const m = n - (n % s);
103
104 if (n < s) {
105 univariate_accumulator<T, Stats> scalar_acc;
106 for (; first < last; ++first) {
107 scalar_acc(std::invoke(std::forward<F>(f), *first));
108 }
109 return univariate_statistics(scalar_acc);
110 }
111
112 univariate_accumulator<wide, Stats> acc;
113 for (size_t i = 0; i < m; i += s) {
114 acc(detail::load<wide>(first, std::forward<F>(f)));
115 detail::advance(s, first);
116 }
117
118 // gather the remaining values with a scalar accumulator
119 if (m < n) {
120 auto [sw, sx, sxx] = acc.stats();
121 auto scalar_acc = univariate_accumulator<T, Stats>::load_state(sw, sx, sxx);
122 for (; first < last; ++first) {
123 scalar_acc(std::invoke(std::forward<F>(f), *first));
124 }
125 return univariate_statistics(scalar_acc);
126 }
127 return univariate_statistics(acc);
128}
129
143template<std::floating_point T,
144 stats Stats = stats::variance,
145 std::random_access_iterator I,
146 std::random_access_iterator J,
147 typename F = std::identity>
148 requires concepts::arithmetic_projection<F, std::iter_value_t<I>> and std::is_arithmetic_v<std::iter_value_t<J>>
149inline auto accumulate(I first1, I last1, J first2, F&& f = F {}) noexcept
151{
152 using wide = eve::wide<T>;
153 auto constexpr s {wide::size()};
154 auto const n {std::distance(first1, last1)};
155 const size_t m = n - n % s;
156
157 if (n < s) {
158 univariate_accumulator<T, Stats> scalar_acc;
159 for (; first1 < last1; ++first1, ++first2) {
160 scalar_acc(std::invoke(std::forward<F>(f), *first1), *first2);
161 }
162 return univariate_statistics(scalar_acc);
163 }
164
165 univariate_accumulator<wide, Stats> acc;
166 for (size_t i = 0; i < m; i += s) {
167 acc(detail::load<wide>(first1, std::forward<F>(f)), wide {first2});
168 detail::advance(s, first1, first2);
169 }
170
171 // use a scalar accumulator to gather the remaining values
172 if (m < n) {
173 auto [sw, sx, sxx] = acc.stats();
174 auto scalar_acc = univariate_accumulator<T, Stats>::load_state(sw, sx, sxx);
175 for (; first1 < last1; ++first1, ++first2) {
176 scalar_acc(std::invoke(std::forward<F>(f), *first1), *first2);
177 }
178 return univariate_statistics(scalar_acc);
179 }
180 return univariate_statistics(acc);
181}
182
213template<std::floating_point T,
214 stats Stats = stats::variance,
215 nan_policy Policy = nan_policy::propagate,
216 std::random_access_iterator I,
217 std::random_access_iterator J,
218 typename BinaryOp,
219 typename F1 = std::identity,
220 typename F2 = std::identity>
221 requires std::is_invocable_v<F1, std::iter_value_t<I>> and std::is_invocable_v<F2, std::iter_value_t<J>>
222 and std::is_invocable_v<BinaryOp,
223 std::invoke_result_t<F1, std::iter_value_t<I>>,
224 std::invoke_result_t<F2, std::iter_value_t<J>>>
225 and concepts::arithmetic_projection<BinaryOp,
226 std::invoke_result_t<F1, std::iter_value_t<I>>,
227 std::invoke_result_t<F2, std::iter_value_t<J>>>
228inline auto accumulate(I first1,
229 I last1,
230 J first2,
231 BinaryOp&& op = BinaryOp {},
232 F1&& f1 = F1 {},
233 F2&& f2 = F2 {}) noexcept
234 -> std::conditional_t<Policy == nan_policy::omit, std::pair<univariate_statistics, std::size_t>, univariate_statistics>
235{
236 using wide = eve::wide<T>;
237 auto constexpr s {wide::size()};
238 auto const n {std::distance(first1, last1)};
239 auto const m = n - n % s;
240
241 auto f = [&](auto a, auto b)
242 {
243 return std::invoke(
244 std::forward<BinaryOp>(op), std::invoke(std::forward<F1>(f1), a), std::invoke(std::forward<F2>(f2), b));
245 };
246
247 if constexpr (Policy == nan_policy::propagate) {
248 if (n < s) {
249 univariate_accumulator<T, Stats> scalar_acc;
250 for (; first1 < last1; ++first1, ++first2) {
251 scalar_acc(f(*first1, *first2));
252 }
253 return univariate_statistics(scalar_acc);
254 }
255
256 univariate_accumulator<wide, Stats> acc;
257 for (size_t i = 0; i < m; i += s) {
258 acc(detail::load<wide>(first1, first2, f));
259 detail::advance(s, first1, first2);
260 }
261
262 // use a scalar accumulator to gather the remaining values
263 if (m < n) {
264 auto [sw, sx, sxx] = acc.stats();
265 auto scalar_acc = univariate_accumulator<T, Stats>::load_state(sw, sx, sxx);
266 for (; first1 < last1; ++first1, ++first2) {
267 scalar_acc(f(*first1, *first2));
268 }
269 return univariate_statistics(scalar_acc);
270 }
271 return univariate_statistics(acc);
272 } else {
273 univariate_accumulator<wide, Stats> acc;
274 wide skipped {0};
275 for (size_t i = 0; i < m; i += s) {
276 wide a {first1};
277 wide b {first2};
278 // is_finite(x) is is_not_nan(x - x) (per eve's docs): one
279 // is_finite call on (a-a)+(b-b) instead of two calls + a
280 // logical-and.
281 auto finite = eve::is_finite((a - a) + (b - b));
282 if (eve::all(finite)) [[likely]] {
283 acc(f(a, b));
284 } else {
285 // sanitize values, not just weight: NaN/Inf * 0 == NaN
286 wide sa = eve::if_else(finite, a, wide {0});
287 wide sb = eve::if_else(finite, b, wide {0});
288 wide w = eve::if_else(finite, wide {1}, wide {0});
289 acc(f(sa, sb), w);
290 skipped += eve::if_else(finite, wide {0}, wide {1});
291 }
292 detail::advance(s, first1, first2);
293 }
294
295 auto se = univariate_accumulator<T, Stats>::load_state(acc.stats());
296 auto skipped_count = static_cast<std::size_t>(eve::reduce(skipped));
297 for (; first1 < last1; ++first1, ++first2) {
298 if (std::isfinite(*first1) && std::isfinite(*first2)) [[likely]] {
299 // unweighted overload: cheaper recurrence than weighted(x, 1)
300 se(f(*first1, *first2));
301 } else {
302 // a w=0 contribution is a no-op on accumulator state (x*0,
303 // sum_w += 0, guarded denom == 0) -- skip the call entirely
304 // rather than paying for a masked weighted() call.
305 ++skipped_count;
306 }
307 }
308 return {univariate_statistics(se), skipped_count};
309 }
310}
311
321template<std::floating_point T,
322 stats Stats = stats::variance,
323 nan_policy Policy = nan_policy::propagate,
324 std::random_access_iterator I,
325 std::random_access_iterator J,
326 std::random_access_iterator K,
327 typename BinaryOp,
328 typename F1 = std::identity,
329 typename F2 = std::identity>
330 requires std::is_arithmetic_v<std::iter_value_t<K>> && std::is_invocable_v<F1, std::iter_value_t<I>>
331 && std::is_invocable_v<F2, std::iter_value_t<J>>
332 && std::is_invocable_v<BinaryOp,
333 std::invoke_result_t<F1, std::iter_value_t<I>>,
334 std::invoke_result_t<F2, std::iter_value_t<J>>>
335 && concepts::arithmetic_projection<BinaryOp,
336 std::invoke_result_t<F1, std::iter_value_t<I>>,
337 std::invoke_result_t<F2, std::iter_value_t<J>>>
338inline auto accumulate(I first1,
339 I last1,
340 J first2,
341 K first3,
342 BinaryOp&& op = BinaryOp {},
343 F1&& f1 = F1 {},
344 F2&& f2 = F2 {}) noexcept
345 -> std::conditional_t<Policy == nan_policy::omit, std::pair<univariate_statistics, std::size_t>, univariate_statistics>
346{
347 using wide = eve::wide<T>;
348 auto constexpr s {wide::size()};
349 auto const n {std::distance(first1, last1)};
350 auto const m = n - n % s;
351
352 auto f = [&](auto a, auto b)
353 {
354 return std::invoke(
355 std::forward<BinaryOp>(op), std::invoke(std::forward<F1>(f1), a), std::invoke(std::forward<F2>(f2), b));
356 };
357
358 if constexpr (Policy == nan_policy::propagate) {
359 if (n < s) {
360 univariate_accumulator<T, Stats> scalar_acc;
361 for (; first1 < last1; ++first1, ++first2, ++first3) {
362 scalar_acc(f(*first1, *first2), *first3);
363 }
364 return univariate_statistics(scalar_acc);
365 }
366
367 univariate_accumulator<wide, Stats> acc;
368 for (size_t i = 0; i < m; i += s) {
369 acc(detail::load<wide>(first1, first2, f), wide {std::to_address(first3)});
370 detail::advance(s, first1, first2, first3);
371 }
372
373 // use a scalar accumulator to gather the remaining values
374 if (m < n) {
375 auto [sw, sx, sxx] = acc.stats();
376 auto scalar_acc = univariate_accumulator<T, Stats>::load_state(sw, sx, sxx);
377 for (; first1 < last1; ++first1, ++first2, ++first3) {
378 scalar_acc(f(*first1, *first2), *first3);
379 }
380 return univariate_statistics(scalar_acc);
381 }
382 return univariate_statistics(acc);
383 } else {
384 univariate_accumulator<wide, Stats> acc;
385 wide skipped {0};
386 for (size_t i = 0; i < m; i += s) {
387 wide a {first1};
388 wide b {first2};
389 wide weight {first3};
390 auto finite = eve::is_finite((a - a) + (b - b));
391 if (eve::all(finite)) [[likely]] {
392 acc(f(a, b), weight);
393 } else {
394 // sanitize values, not just weight: NaN/Inf * 0 == NaN
395 wide sa = eve::if_else(finite, a, wide {0});
396 wide sb = eve::if_else(finite, b, wide {0});
397 wide w = eve::if_else(finite, weight, wide {0});
398 acc(f(sa, sb), w);
399 skipped += eve::if_else(finite, wide {0}, wide {1});
400 }
401 detail::advance(s, first1, first2, first3);
402 }
403
404 auto se = univariate_accumulator<T, Stats>::load_state(acc.stats());
405 auto skipped_count = static_cast<std::size_t>(eve::reduce(skipped));
406 for (; first1 < last1; ++first1, ++first2, ++first3) {
407 if (std::isfinite(*first1) && std::isfinite(*first2)) [[likely]] {
408 se(f(*first1, *first2), *first3);
409 } else {
410 // a w=0 contribution is a no-op on accumulator state -- skip
411 // the call entirely rather than paying for a masked one.
412 ++skipped_count;
413 }
414 }
415 return {univariate_statistics(se), skipped_count};
416 }
417}
418} // namespace univariate
419
420namespace bivariate
421{
475template<std::floating_point T,
476 nan_policy Policy = nan_policy::propagate,
477 std::random_access_iterator I,
478 std::random_access_iterator J,
479 typename F1 = std::identity,
480 typename F2 = std::identity>
481 requires concepts::arithmetic_projection<F1, std::iter_value_t<I>>
482 and concepts::arithmetic_projection<F2, std::iter_value_t<J>>
483inline auto accumulate(I first1, I last1, J first2, F1&& f1 = F1 {}, F2&& f2 = F2 {}) noexcept
484 -> std::conditional_t<Policy == nan_policy::omit, std::pair<bivariate_statistics, std::size_t>, bivariate_statistics>
485{
486 using wide = eve::wide<T>;
487 auto constexpr s {wide::size()};
488 auto const n {std::distance(first1, last1)};
489 auto const m = n - n % s;
490
491 if constexpr (Policy == nan_policy::propagate) {
492 if (n < s) {
493 bivariate_accumulator<T> scalar_acc;
494 for (; first1 < last1; ++first1, ++first2) {
495 scalar_acc(std::invoke(std::forward<F1>(f1), *first1), std::invoke(std::forward<F2>(f2), *first2));
496 }
497 return bivariate_statistics(scalar_acc);
498 }
499
500 bivariate_accumulator<wide> acc;
501 for (size_t i = 0; i < m; i += s) {
502 acc(detail::load<wide>(first1, std::forward<F1>(f1)), detail::load<wide>(first2, std::forward<F2>(f2)));
503 detail::advance(s, first1, first2);
504 }
505
506 if (m < n) {
507 auto [sw, sx, sy, sxx, syy, sxy] = acc.stats();
508 auto scalar_acc = bivariate_accumulator<T>::load_state(sx, sy, sw, sxx, syy, sxy);
509 for (; first1 < last1; ++first1, ++first2) {
510 scalar_acc(std::invoke(std::forward<F1>(f1), *first1), std::invoke(std::forward<F2>(f2), *first2));
511 }
512 return bivariate_statistics(scalar_acc);
513 }
514
515 return bivariate_statistics(acc);
516 } else {
517 bivariate_accumulator<wide> acc;
518 wide skipped {0};
519 for (size_t i = 0; i < m; i += s) {
520 wide a {first1};
521 wide b {first2};
522 auto finite = eve::is_finite((a - a) + (b - b));
523 if (eve::all(finite)) [[likely]] {
524 // Match plain accumulate's call shape (2-arg unweighted),
525 // which delegates to the weighted overload with w=1 --
526 // keeps one source of truth for the Welford update.
527 acc(std::invoke(f1, a), std::invoke(f2, b));
528 } else {
529 wide sa = eve::if_else(finite, a, wide {0});
530 wide sb = eve::if_else(finite, b, wide {0});
531 wide w = eve::if_else(finite, wide {1}, wide {0});
532 acc(std::invoke(f1, sa), std::invoke(f2, sb), w);
533 skipped += eve::if_else(finite, wide {0}, wide {1});
534 }
535 detail::advance(s, first1, first2);
536 }
537
538 auto [sw, sx, sy, sxx, syy, sxy] = acc.stats();
539 auto be = bivariate_accumulator<T>::load_state(sx, sy, sw, sxx, syy, sxy);
540 auto skipped_count = static_cast<std::size_t>(eve::reduce(skipped));
541 for (; first1 < last1; ++first1, ++first2) {
542 if (std::isfinite(*first1) && std::isfinite(*first2)) [[likely]] {
543 be(std::invoke(f1, *first1), std::invoke(f2, *first2));
544 } else {
545 // a w=0 contribution is a no-op on accumulator state -- skip
546 // the call entirely rather than paying for a masked one.
547 ++skipped_count;
548 }
549 }
550 return {bivariate_statistics(be), skipped_count};
551 }
552}
553
561template<std::floating_point T,
562 nan_policy Policy = nan_policy::propagate,
563 std::random_access_iterator I,
564 std::random_access_iterator J,
565 std::random_access_iterator K,
566 typename F1 = std::identity,
567 typename F2 = std::identity>
568 requires concepts::arithmetic_projection<F1, std::iter_value_t<I>>
569 and concepts::arithmetic_projection<F2, std::iter_value_t<J>> and std::is_arithmetic_v<std::iter_value_t<K>>
570inline auto accumulate(
571 I first1, I last1, J first2, K first3, F1&& f1 = F1 {}, F2&& f2 = F2 {}) noexcept
572 -> std::conditional_t<Policy == nan_policy::omit, std::pair<bivariate_statistics, std::size_t>, bivariate_statistics>
573{
574 using wide = eve::wide<T>;
575 auto constexpr s {wide::size()};
576 auto const n = std::distance(first1, last1);
577 auto const m = n - n % s;
578
579 if constexpr (Policy == nan_policy::propagate) {
580 if (n < s) {
581 bivariate_accumulator<T> scalar_acc;
582 for (; first1 < last1; ++first1, ++first2, ++first3) {
583 scalar_acc(
584 std::invoke(std::forward<F1>(f1), *first1), std::invoke(std::forward<F2>(f2), *first2), *first3);
585 }
586 return bivariate_statistics(scalar_acc);
587 }
588
589 bivariate_accumulator<wide> acc;
590 for (size_t i = 0; i < m; i += s) {
591 acc(detail::load<wide>(first1, std::forward<F1>(f1)),
592 detail::load<wide>(first2, std::forward<F2>(f2)),
593 wide {first3});
594 detail::advance(s, first1, first2, first3);
595 }
596
597 if (m < n) {
598 auto [sw, sx, sy, sxx, syy, sxy] = acc.stats();
599 auto scalar_acc = bivariate_accumulator<T>::load_state(sx, sy, sw, sxx, syy, sxy);
600 for (; first1 < last1; ++first1, ++first2, ++first3) {
601 scalar_acc(std::invoke(std::forward<F1>(f1), *first1), std::invoke(std::forward<F2>(f2), *first2), *first3);
602 }
603 return bivariate_statistics(scalar_acc);
604 }
605 return bivariate_statistics(acc);
606 } else {
607 bivariate_accumulator<wide> acc;
608 wide skipped {0};
609 for (size_t i = 0; i < m; i += s) {
610 wide a {first1};
611 wide b {first2};
612 wide weight {first3};
613 auto finite = eve::is_finite((a - a) + (b - b));
614 if (eve::all(finite)) [[likely]] {
615 acc(std::invoke(f1, a), std::invoke(f2, b), weight);
616 } else {
617 wide sa = eve::if_else(finite, a, wide {0});
618 wide sb = eve::if_else(finite, b, wide {0});
619 wide w = eve::if_else(finite, weight, wide {0});
620 acc(std::invoke(f1, sa), std::invoke(f2, sb), w);
621 skipped += eve::if_else(finite, wide {0}, wide {1});
622 }
623 detail::advance(s, first1, first2, first3);
624 }
625
626 auto [sw, sx, sy, sxx, syy, sxy] = acc.stats();
627 auto be = bivariate_accumulator<T>::load_state(sx, sy, sw, sxx, syy, sxy);
628 auto skipped_count = static_cast<std::size_t>(eve::reduce(skipped));
629 for (; first1 < last1; ++first1, ++first2, ++first3) {
630 if (std::isfinite(*first1) && std::isfinite(*first2)) [[likely]] {
631 be(std::invoke(f1, *first1), std::invoke(f2, *first2), *first3);
632 } else {
633 // a w=0 contribution is a no-op on accumulator state -- skip
634 // the call entirely rather than paying for a masked one.
635 ++skipped_count;
636 }
637 }
638 return {bivariate_statistics(be), skipped_count};
639 }
640}
641} // namespace bivariate
642
643namespace metrics
644{
665template<std::floating_point T, std::contiguous_iterator I, std::contiguous_iterator J>
666inline auto r2_score(I first1, I last1, J first2) noexcept -> double
667{
668 using wide = eve::wide<T>;
669 auto constexpr s {wide::size()};
670 auto const n {std::distance(first1, last1)};
671 auto const m {n - (n % s)};
672
675 for (auto i = 0; i < m; i += s) {
676 wide y_true {first1};
677 wide y_pred {first2};
678 wx(eve::sqr(y_true - y_pred));
679 wy(y_true);
680 detail::advance(s, first1, first2);
681 }
682
683 // use scalar accumulators for the remaining values
686
687 for (; first1 < last1; ++first1, ++first2) {
688 sx(eve::sqr(*first1 - *first2));
689 sy(*first1);
690 }
691
692 auto const rss = univariate_statistics(sx).sum;
693 auto const tss = univariate_statistics(sy).ssr;
694
695 return tss < std::numeric_limits<double>::epsilon() ? std::numeric_limits<double>::lowest() : 1.0 - (rss / tss);
696}
697
709template<std::floating_point T, std::contiguous_iterator I, std::contiguous_iterator J, std::contiguous_iterator K>
710inline auto r2_score(I first1, I last1, J first2, K first3) noexcept -> double
711{
712 using wide = eve::wide<T>;
713 auto constexpr s {wide::size()};
714 auto const n {std::distance(first1, last1)};
715 auto const m {n - (n % s)};
716
719 for (auto i = 0; i < m; i += s) {
720 wide y_true {first1};
721 wide y_pred {first2};
722 wide weight {first3};
723 wx(eve::sqr(y_true - y_pred), weight);
724 wy(y_true, weight);
725 detail::advance(s, first1, first2, first3);
726 }
727
728 // use scalar accumulators for the remaining values
731
732 for (; first1 < last1; ++first1, ++first2, ++first3) {
733 sx(eve::sqr(*first1 - *first2), *first3);
734 sy(*first1, *first3);
735 }
736
737 auto const rss = univariate_statistics(sx).sum;
738 auto const tss = univariate_statistics(sy).ssr;
739
740 return tss < std::numeric_limits<double>::epsilon() ? std::numeric_limits<double>::lowest() : 1.0 - rss / tss;
741}
742
760template<std::floating_point T, nan_policy Policy = nan_policy::propagate, std::contiguous_iterator I, std::contiguous_iterator J>
761inline auto mean_squared_error(I first1, I last1, J first2) noexcept
762 -> std::conditional_t<Policy == nan_policy::omit, std::pair<double, std::size_t>, double>
763{
764 if constexpr (Policy == nan_policy::propagate) {
765 using wide = eve::wide<T>;
766 auto constexpr s {wide::size()};
767 auto const n {std::distance(first1, last1)};
768 auto const m {n - n % s};
769
771 for (auto i = 0; i < m; i += s) {
772 wide y_true {first1};
773 wide y_pred {first2};
774 we(eve::sqr(y_true - y_pred));
775 detail::advance(s, first1, first2);
776 }
777
778 // use scalar accumulators for the remaining values
780 for (; first1 < last1; ++first1, ++first2) {
781 se(eve::sqr(*first1 - *first2));
782 }
783 return univariate_statistics(se).mean;
784 } else {
785 auto [st, skipped] = univariate::accumulate<T, stats::mean, nan_policy::omit>(
786 first1, last1, first2, [](auto a, auto b) { return eve::sqr(a - b); });
787 return {st.mean, skipped};
788 }
789}
790
801template<std::floating_point T, nan_policy Policy = nan_policy::propagate, std::contiguous_iterator I, std::contiguous_iterator J, std::contiguous_iterator K>
802inline auto mean_squared_error(I first1, I last1, J first2, K first3) noexcept
803 -> std::conditional_t<Policy == nan_policy::omit, std::pair<double, std::size_t>, double>
804{
805 if constexpr (Policy == nan_policy::propagate) {
806 using wide = eve::wide<T>;
807 auto constexpr s {wide::size()};
808 auto const n {std::distance(first1, last1)};
809 auto const m {n - n % s};
810
812 for (auto i = 0; i < m; i += s) {
813 wide y_true {first1};
814 wide y_pred {first2};
815 wide weight {first3};
816 we(eve::sqr(y_true - y_pred), weight);
817 detail::advance(s, first1, first2, first3);
818 }
819
820 // use scalar accumulators for the remaining values
822 for (; first1 < last1; ++first1, ++first2, ++first3) {
823 se(eve::sqr(*first1 - *first2), *first3);
824 }
825 return univariate_statistics(se).mean;
826 } else {
827 auto [st, skipped] = univariate::accumulate<T, stats::mean, nan_policy::omit>(
828 first1, last1, first2, first3, [](auto a, auto b) { return eve::sqr(a - b); });
829 return {st.mean, skipped};
830 }
831}
832
859template<std::floating_point T, nan_policy Policy = nan_policy::propagate, std::contiguous_iterator I, std::contiguous_iterator J>
860inline auto normalized_mean_squared_error(I first1, I last1, J first2) noexcept
861 -> std::conditional_t<Policy == nan_policy::omit, std::pair<double, std::size_t>, double>
862{
863 using wide = eve::wide<T>;
864 auto constexpr s {wide::size()};
865 auto const n {std::distance(first1, last1)};
866 auto const m = n - n % s;
867
868 univariate_accumulator<wide, stats::mean> we; // residual mean: <(a-b)^2>
869 univariate_accumulator<wide, stats::variance> wv; // target variance: Var(b)
870
871 if constexpr (Policy == nan_policy::propagate) {
872 for (size_t i = 0; i < m; i += s) {
873 wide a {first1};
874 wide b {first2};
875 we(eve::sqr(a - b));
876 wv(b);
877 detail::advance(s, first1, first2);
878 }
879
882 for (; first1 < last1; ++first1, ++first2) {
883 se(eve::sqr(*first1 - *first2));
884 sv(*first2);
885 }
886
887 auto const mean = univariate_statistics(se).mean;
888 auto const var = univariate_statistics(sv).variance;
889 return var > 0.0 ? mean / var : 0.0;
890 } else {
891 wide skipped {0};
892 for (size_t i = 0; i < m; i += s) {
893 wide a {first1};
894 wide b {first2};
895 auto finite = eve::is_finite((a - a) + (b - b));
896 if (eve::all(finite)) [[likely]] {
897 we(eve::sqr(a - b));
898 wv(b);
899 } else {
900 wide sa = eve::if_else(finite, a, wide {0});
901 wide sb = eve::if_else(finite, b, wide {0});
902 wide w = eve::if_else(finite, wide {1}, wide {0});
903 // mask carried by weight -- the weighted overload's
904 // zero-denominator guard handles lanes whose first
905 // contribution is zero-weighted without NaN-poisoning the
906 // accumulator state.
907 we(eve::sqr(sa - sb), w);
908 wv(sb, w);
909 skipped += eve::if_else(finite, wide {0}, wide {1});
910 }
911 detail::advance(s, first1, first2);
912 }
913
916 auto skipped_count = static_cast<std::size_t>(eve::reduce(skipped));
917 for (; first1 < last1; ++first1, ++first2) {
918 if (std::isfinite(*first1) && std::isfinite(*first2)) [[likely]] {
919 se(eve::sqr(*first1 - *first2));
920 sv(*first2);
921 } else {
922 // a w=0 contribution is a no-op on accumulator state -- skip
923 // the calls entirely rather than paying for masked ones.
924 ++skipped_count;
925 }
926 }
927
928 auto const mean = univariate_statistics(se).mean;
929 auto const var = univariate_statistics(sv).variance;
930 return {var > 0.0 ? mean / var : 0.0, skipped_count};
931 }
932}
933
939template<std::floating_point T, nan_policy Policy = nan_policy::propagate, std::contiguous_iterator I, std::contiguous_iterator J, std::contiguous_iterator K>
940inline auto normalized_mean_squared_error(I first1, I last1, J first2, K first3) noexcept
941 -> std::conditional_t<Policy == nan_policy::omit, std::pair<double, std::size_t>, double>
942{
943 using wide = eve::wide<T>;
944 auto constexpr s {wide::size()};
945 auto const n {std::distance(first1, last1)};
946 auto const m = n - n % s;
947
950
951 if constexpr (Policy == nan_policy::propagate) {
952 for (size_t i = 0; i < m; i += s) {
953 wide a {first1};
954 wide b {first2};
955 wide weight {first3};
956 we(eve::sqr(a - b), weight);
957 wv(b, weight);
958 detail::advance(s, first1, first2, first3);
959 }
960
963 for (; first1 < last1; ++first1, ++first2, ++first3) {
964 se(eve::sqr(*first1 - *first2), *first3);
965 sv(*first2, *first3);
966 }
967
968 auto const mean = univariate_statistics(se).mean;
969 auto const var = univariate_statistics(sv).variance;
970 return var > 0.0 ? mean / var : 0.0;
971 } else {
972 wide skipped {0};
973 for (size_t i = 0; i < m; i += s) {
974 wide a {first1};
975 wide b {first2};
976 wide weight {first3};
977 auto finite = eve::is_finite((a - a) + (b - b));
978 if (eve::all(finite)) [[likely]] {
979 we(eve::sqr(a - b), weight);
980 wv(b, weight);
981 } else {
982 wide sa = eve::if_else(finite, a, wide {0});
983 wide sb = eve::if_else(finite, b, wide {0});
984 wide w = eve::if_else(finite, weight, wide {0});
985 we(eve::sqr(sa - sb), w);
986 wv(sb, w);
987 skipped += eve::if_else(finite, wide {0}, wide {1});
988 }
989 detail::advance(s, first1, first2, first3);
990 }
991
994 auto skipped_count = static_cast<std::size_t>(eve::reduce(skipped));
995 for (; first1 < last1; ++first1, ++first2, ++first3) {
996 if (std::isfinite(*first1) && std::isfinite(*first2)) [[likely]] {
997 se(eve::sqr(*first1 - *first2), *first3);
998 sv(*first2, *first3);
999 } else {
1000 // a w=0 contribution is a no-op on accumulator state -- skip
1001 // the calls entirely rather than paying for masked ones.
1002 ++skipped_count;
1003 }
1004 }
1005
1006 auto const mean = univariate_statistics(se).mean;
1007 auto const var = univariate_statistics(sv).variance;
1008 return {var > 0.0 ? mean / var : 0.0, skipped_count};
1009 }
1010}
1011
1022template<std::floating_point T, std::contiguous_iterator I, std::contiguous_iterator J>
1023inline auto mean_squared_log_error(I first1, I last1, J first2) noexcept -> double
1024{
1025 using wide = eve::wide<T>;
1026 auto constexpr s {wide::size()};
1027 auto const n {std::distance(first1, last1)};
1028 auto const m {n - n % s};
1029
1031 for (auto i = 0; i < m; i += s) {
1032 wide y_true {first1};
1033 wide y_pred {first2};
1034 we(eve::sqr(eve::log1p(y_true) - eve::log1p(y_pred)));
1035 detail::advance(s, first1, first2);
1036 }
1037
1038 // use scalar accumulators for the remaining values
1040 for (; first1 < last1; ++first1, ++first2) {
1041 se(eve::sqr(eve::log1p(*first1) - eve::log1p(*first2)));
1042 }
1043 return univariate_statistics(se).mean;
1044}
1045
1056template<std::floating_point T, std::contiguous_iterator I, std::contiguous_iterator J, std::contiguous_iterator K>
1057inline auto mean_squared_log_error(I first1, I last1, J first2, K first3) noexcept -> double
1058{
1059 using wide = eve::wide<T>;
1060 auto constexpr s {wide::size()};
1061 auto const n {std::distance(first1, last1)};
1062 auto const m {n - n % s};
1063
1065 for (auto i = 0; i < m; i += s) {
1066 wide y_true {first1};
1067 wide y_pred {first2};
1068 wide weight {first3};
1069 we(eve::sqr(eve::log1p(y_true) - eve::log1p(y_pred)), weight);
1070 detail::advance(s, first1, first2, first3);
1071 }
1072
1073 // use scalar accumulators for the remaining values
1075 for (; first1 < last1; ++first1, ++first2, ++first3) {
1076 se(eve::sqr(eve::log1p(*first1) - eve::log1p(*first2)), *first3);
1077 }
1078 return univariate_statistics(se).mean;
1079}
1080
1098template<std::floating_point T, nan_policy Policy = nan_policy::propagate, std::contiguous_iterator I, std::contiguous_iterator J>
1099inline auto mean_absolute_error(I first1, I last1, J first2) noexcept
1100 -> std::conditional_t<Policy == nan_policy::omit, std::pair<double, std::size_t>, double>
1101{
1102 if constexpr (Policy == nan_policy::propagate) {
1103 using wide = eve::wide<T>;
1104 auto constexpr s {wide::size()};
1105 auto const n {std::distance(first1, last1)};
1106 auto const m {n - n % s};
1107
1109 for (auto i = 0; i < m; i += s) {
1110 wide y_true {first1};
1111 wide y_pred {first2};
1112 we(eve::abs(y_true - y_pred));
1113 detail::advance(s, first1, first2);
1114 }
1115
1116 // use scalar accumulators for the remaining values
1118 for (; first1 < last1; ++first1, ++first2) {
1119 se(eve::abs(*first1 - *first2));
1120 }
1121 return univariate_statistics(se).mean;
1122 } else {
1123 auto [st, skipped] = univariate::accumulate<T, stats::mean, nan_policy::omit>(
1124 first1, last1, first2, [](auto a, auto b) { return eve::abs(a - b); });
1125 return {st.mean, skipped};
1126 }
1127}
1128
1139template<std::floating_point T, nan_policy Policy = nan_policy::propagate, std::contiguous_iterator I, std::contiguous_iterator J, std::contiguous_iterator K>
1140inline auto mean_absolute_error(I first1, I last1, J first2, K first3) noexcept
1141 -> std::conditional_t<Policy == nan_policy::omit, std::pair<double, std::size_t>, double>
1142{
1143 if constexpr (Policy == nan_policy::propagate) {
1144 using wide = eve::wide<T>;
1145 auto constexpr s {wide::size()};
1146 auto const n {std::distance(first1, last1)};
1147 auto const m {n - n % s};
1148
1150 for (auto i = 0; i < m; i += s) {
1151 wide y_true {first1};
1152 wide y_pred {first2};
1153 wide weight {first3};
1154 we(eve::abs(y_true - y_pred), weight);
1155 detail::advance(s, first1, first2, first3);
1156 }
1157
1158 // use scalar accumulators for the remaining values
1160 for (; first1 < last1; ++first1, ++first2, ++first3) {
1161 se(eve::abs(*first1 - *first2), *first3);
1162 }
1163 return univariate_statistics(se).mean;
1164 } else {
1165 auto [st, skipped] = univariate::accumulate<T, stats::mean, nan_policy::omit>(
1166 first1, last1, first2, first3, [](auto a, auto b) { return eve::abs(a - b); });
1167 return {st.mean, skipped};
1168 }
1169}
1170
1185template<std::floating_point T, std::contiguous_iterator I, std::contiguous_iterator J>
1186inline auto mean_absolute_percentage_error(I first1, I last1, J first2) noexcept -> double
1187{
1188 using wide = eve::wide<T>;
1189 auto constexpr s {wide::size()};
1190 auto const n {std::distance(first1, last1)};
1191 auto const m {n - n % s};
1192
1193 auto constexpr eps {std::numeric_limits<T>::epsilon()};
1194
1196 for (auto i = 0; i < m; i += s) {
1197 wide y_true {first1};
1198 wide y_pred {first2};
1199 we(eve::abs(y_true - y_pred) / eve::max(eps, eve::abs(y_true)));
1200 detail::advance(s, first1, first2);
1201 }
1202
1203 // use scalar accumulators for the remaining values
1205 for (; first1 < last1; ++first1, ++first2) {
1206 se(eve::abs(*first1 - *first2) / eve::max(eps, eve::abs(*first1)));
1207 }
1208 return univariate_statistics(se).mean;
1209}
1210
1221template<std::floating_point T, std::contiguous_iterator I, std::contiguous_iterator J, std::contiguous_iterator K>
1222inline auto mean_absolute_percentage_error(I first1, I last1, J first2, K first3) noexcept
1223 -> double
1224{
1225 using wide = eve::wide<T>;
1226 auto constexpr s {wide::size()};
1227 auto const n {std::distance(first1, last1)};
1228 auto const m {n - n % s};
1229
1230 auto constexpr eps {std::numeric_limits<T>::epsilon()};
1231
1233 for (auto i = 0; i < m; i += s) {
1234 wide y_true {first1};
1235 wide y_pred {first2};
1236 wide weight {first3};
1237 we(eve::abs(y_true - y_pred) / eve::max(eps, eve::abs(y_true)), weight);
1238 detail::advance(s, first1, first2, first3);
1239 }
1240
1241 // use scalar accumulators for the remaining values
1243 for (; first1 < last1; ++first1, ++first2, ++first3) {
1244 se(eve::abs(*first1 - *first2) / eve::max(eps, eve::abs(*first1)), *first3);
1245 }
1246 return univariate_statistics(se).mean;
1247}
1248
1260template<std::floating_point T, std::contiguous_iterator I, std::contiguous_iterator J>
1261inline auto poisson_neg_likelihood_loss(I first1, I last1, J first2) noexcept -> double
1262{
1263 using wide = eve::wide<T>;
1264 auto constexpr s {wide::size()};
1265 auto const n {std::distance(first1, last1)};
1266 auto const m {n - n % s};
1267
1269 for (auto i = 0; i < m; i += s) {
1270 wide y_true {first1};
1271 wide y_pred {first2};
1272 we(y_pred - y_true * eve::log(y_pred) + eve::log_abs_gamma(T {1} + y_true));
1273 detail::advance(s, first1, first2);
1274 }
1275
1276 // use scalar accumulators for the remaining values
1278 for (; first1 < last1; ++first1, ++first2) {
1279 se(*first2 - *first1 * eve::log(*first2) + eve::log_abs_gamma(T {1} + *first1));
1280 }
1281 return univariate_statistics(se).sum;
1282}
1283
1297template<std::floating_point T, std::contiguous_iterator I, std::contiguous_iterator J, std::contiguous_iterator K>
1298inline auto poisson_neg_likelihood_loss(I first1, I last1, J first2, K first3) noexcept
1299 -> double
1300{
1301 using wide = eve::wide<T>;
1302 auto constexpr s {wide::size()};
1303 auto const n {std::distance(first1, last1)};
1304 auto const m {n - n % s};
1305
1307 for (auto i = 0; i < m; i += s) {
1308 wide y_true {first1};
1309 wide y_pred = wide {first2} * wide {first3};
1310 we(y_pred - y_true * eve::log(y_pred) + eve::log_abs_gamma(T {1} + y_true));
1311 detail::advance(s, first1, first2, first3);
1312 }
1313
1314 // use scalar accumulators for the remaining values
1316 for (; first1 < last1; ++first1, ++first2, ++first3) {
1317 se(*first2 * *first3 - *first1 * eve::log(*first2 * *first3) + eve::log_abs_gamma(T {1} + *first1));
1318 }
1319 return univariate_statistics(se).sum;
1320}
1321
1336template<std::floating_point T, std::contiguous_iterator I, std::contiguous_iterator J>
1337inline auto gaussian_neg_likelihood_loss(I first1, I last1, J first2, T sigma) noexcept
1338 -> double
1339{
1340 using wide = eve::wide<T>;
1341 auto constexpr s {wide::size()};
1342 auto const n {std::distance(first1, last1)};
1343 auto const m {n - n % s};
1344
1346 for (auto i = 0; i < m; i += s) {
1347 wide y_true {first1};
1348 wide y_pred {first2};
1349 we(eve::sqr(y_true - y_pred));
1350 detail::advance(s, first1, first2);
1351 }
1352
1353 // use scalar accumulators for the remaining values
1355 for (; first1 < last1; ++first1, ++first2) {
1356 se(eve::sqr(*first1 - *first2));
1357 }
1358 auto const ssr = univariate_statistics(se).sum;
1359 auto const pi = std::numbers::pi_v<double>;
1360 return 0.5 * static_cast<double>(n) * std::log(2.0 * pi)
1361 + static_cast<double>(n) * std::log(static_cast<double>(sigma))
1362 + ssr / (2.0 * static_cast<double>(sigma) * static_cast<double>(sigma));
1363}
1364
1377template<std::floating_point T, std::contiguous_iterator I, std::contiguous_iterator J>
1378inline auto poisson_log_neg_likelihood_loss(I first1, I last1, J first2) noexcept -> double
1379{
1380 using wide = eve::wide<T>;
1381 auto constexpr s {wide::size()};
1382 auto const n {std::distance(first1, last1)};
1383 auto const m {n - n % s};
1384
1386 for (auto i = 0; i < m; i += s) {
1387 wide y_true {first1};
1388 wide y_pred {first2};
1389 we(eve::exp(y_pred) - y_true * y_pred + eve::log_abs_gamma(T {1} + y_true));
1390 detail::advance(s, first1, first2);
1391 }
1392
1393 // use scalar accumulators for the remaining values
1395 for (; first1 < last1; ++first1, ++first2) {
1396 se(eve::exp(*first2) - *first1 * *first2 + eve::log_abs_gamma(T {1} + *first1));
1397 }
1398 return univariate_statistics(se).sum;
1399}
1400} // namespace metrics
1401
1402} // namespace VSTAT_NAMESPACE
1403
1404#endif
auto normalized_mean_squared_error(I first1, I last1, J first2) noexcept -> std::conditional_t< Policy==nan_policy::omit, std::pair< double, std::size_t >, double >
Normalized mean squared error over (estimated, target) pairs.
Definition vstat.hpp:860
auto mean_absolute_error(I first1, I last1, J first2) noexcept -> std::conditional_t< Policy==nan_policy::omit, std::pair< double, std::size_t >, double >
Computes the mean absolute error.
Definition vstat.hpp:1099
auto gaussian_neg_likelihood_loss(I first1, I last1, J first2, T sigma) noexcept -> double
Negative log likelihood loss under a Gaussian with known scalar noise level .
Definition vstat.hpp:1337
auto mean_squared_error(I first1, I last1, J first2) noexcept -> std::conditional_t< Policy==nan_policy::omit, std::pair< double, std::size_t >, double >
Computes the mean squared error.
Definition vstat.hpp:761
auto mean_squared_log_error(I first1, I last1, J first2) noexcept -> double
Computes the mean squared logarithmic error.
Definition vstat.hpp:1023
auto poisson_log_neg_likelihood_loss(I first1, I last1, J first2) noexcept -> double
Negative log likelihood loss with Poisson distribution of target, where the model outputs (the natur...
Definition vstat.hpp:1378
auto r2_score(I first1, I last1, J first2) noexcept -> double
Computes the coefficient of determination .
Definition vstat.hpp:666
auto poisson_neg_likelihood_loss(I first1, I last1, J first2) noexcept -> double
Negative log likelihood loss with Poisson distribution of target.
Definition vstat.hpp:1261
auto mean_absolute_percentage_error(I first1, I last1, J first2) noexcept -> double
Computes the mean absolute percentage error.
Definition vstat.hpp:1186
auto accumulate(I first, I last, F &&f=F {}) noexcept -> univariate_statistics
Accumulates a sequence of (projected) values.
Definition vstat.hpp:97
Univariate accumulator object.
Definition univariate.hpp:37
Univariate statistics.
Definition univariate.hpp:131