EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Execution.h
Go to the documentation of this file.
1#pragma once
2
12
13#include <algorithm>
14#include <concepts>
15#include <cstddef>
16#include <iterator>
17#include <span>
18#include <type_traits>
19#include <utility>
20#include <vector>
21
24
25namespace Emrys
26{
27
107
114namespace execution
115{
116
123{};
124
128inline constexpr Sequential sequential{};
129
130} // namespace execution
131
132namespace internal
133{
134
146class CountingIterator
147{
148public:
149
150 using iterator_concept = std::random_access_iterator_tag;
151 using iterator_category = std::random_access_iterator_tag;
152 using value_type = Eigen::Index;
153 using difference_type = Eigen::Index;
154 using pointer = void;
155 using reference = Eigen::Index;
156
157 CountingIterator() = default;
158
160 explicit CountingIterator(Eigen::Index index)
161 : m_index{index}
162 {}
163
165 [[nodiscard]] Eigen::Index
166 operator*() const
167 {
168 return m_index;
169 }
170
172 [[nodiscard]] Eigen::Index
173 operator[](difference_type n) const
174 {
175 return m_index + n;
176 }
177
179 CountingIterator &
180 operator++()
181 {
182 ++m_index;
183 return *this;
184 }
185
187 CountingIterator
188 operator++(int)
189 {
190 CountingIterator const before = *this;
191 ++m_index;
192 return before;
193 }
194
196 CountingIterator &
197 operator--()
198 {
199 --m_index;
200 return *this;
201 }
202
204 CountingIterator
205 operator--(int)
206 {
207 CountingIterator const before = *this;
208 --m_index;
209 return before;
210 }
211
213 CountingIterator &
214 operator+=(difference_type n)
215 {
216 m_index += n;
217 return *this;
218 }
219
221 CountingIterator &
222 operator-=(difference_type n)
223 {
224 m_index -= n;
225 return *this;
226 }
227
229 [[nodiscard]] friend CountingIterator
230 operator+(CountingIterator it, difference_type n)
231 {
232 return CountingIterator{it.m_index + n};
233 }
234
236 [[nodiscard]] friend CountingIterator
237 operator+(difference_type n, CountingIterator it)
238 {
239 return CountingIterator{it.m_index + n};
240 }
241
243 [[nodiscard]] friend CountingIterator
244 operator-(CountingIterator it, difference_type n)
245 {
246 return CountingIterator{it.m_index - n};
247 }
248
250 [[nodiscard]] friend difference_type
251 operator-(CountingIterator a, CountingIterator b)
252 {
253 return a.m_index - b.m_index;
254 }
255
257 [[nodiscard]] friend auto
258 operator<=>(CountingIterator const &, CountingIterator const &) = default;
259
260private:
261
262 Eigen::Index m_index = 0;
263};
264
265static_assert(std::random_access_iterator<CountingIterator>);
266
275template <typename Range>
276class ElementIterator
277{
278public:
279
280 using iterator_concept = std::random_access_iterator_tag;
281 using iterator_category = std::random_access_iterator_tag;
282 using value_type = typename Range::value_type;
283 using difference_type = Eigen::Index;
284 using pointer = void;
285 using reference = value_type;
286
287 ElementIterator() = default;
288
290 ElementIterator(Range const & range, Eigen::Index index)
291 : m_range{&range}
292 , m_index{index}
293 {}
294
296 [[nodiscard]] value_type
297 operator*() const
298 {
299 return (*m_range)[m_index];
300 }
301
303 [[nodiscard]] value_type
304 operator[](difference_type n) const
305 {
306 return (*m_range)[m_index + n];
307 }
308
310 ElementIterator &
311 operator++()
312 {
313 ++m_index;
314 return *this;
315 }
316
318 ElementIterator
319 operator++(int)
320 {
321 ElementIterator const before = *this;
322 ++m_index;
323 return before;
324 }
325
327 ElementIterator &
328 operator--()
329 {
330 --m_index;
331 return *this;
332 }
333
335 ElementIterator
336 operator--(int)
337 {
338 ElementIterator const before = *this;
339 --m_index;
340 return before;
341 }
342
344 ElementIterator &
345 operator+=(difference_type n)
346 {
347 m_index += n;
348 return *this;
349 }
350
352 ElementIterator &
353 operator-=(difference_type n)
354 {
355 m_index -= n;
356 return *this;
357 }
358
360 [[nodiscard]] friend ElementIterator
361 operator+(ElementIterator it, difference_type n)
362 {
363 return it += n;
364 }
365
367 [[nodiscard]] friend ElementIterator
368 operator+(difference_type n, ElementIterator it)
369 {
370 return it += n;
371 }
372
374 [[nodiscard]] friend ElementIterator
375 operator-(ElementIterator it, difference_type n)
376 {
377 return it -= n;
378 }
379
381 [[nodiscard]] friend difference_type
382 operator-(ElementIterator const & a, ElementIterator const & b)
383 {
384 return a.m_index - b.m_index;
385 }
386
388 [[nodiscard]] friend bool
389 operator==(ElementIterator const & a, ElementIterator const & b)
390 {
391 return a.m_index == b.m_index;
392 }
393
395 [[nodiscard]] friend auto
396 operator<=>(ElementIterator const & a, ElementIterator const & b)
397 {
398 return a.m_index <=> b.m_index;
399 }
400
401private:
402
403 Range const * m_range = nullptr;
404 Eigen::Index m_index = 0;
405};
406
417template <typename P>
418concept StandardExecutionPolicy = requires(P && policy, CountingIterator i, void (*f)(Eigen::Index)) {
419 std::for_each(std::forward<P>(policy), i, i, f);
420};
421
425struct IndexFunction
426{
428 void
429 operator()(Eigen::Index i) const;
430};
431
438template <typename P>
439concept UserExecutionPolicy = requires(std::remove_cvref_t<P> const & policy, IndexFunction const & f) {
440 policy.forEachIndex(Eigen::Index{}, f);
441};
442
446template <typename P>
447concept UserPolicyWithAllocator =
448 UserExecutionPolicy<P> && requires(std::remove_cvref_t<P> const & policy) { policy.get_allocator(); };
449
450} // namespace internal
451
458template <typename P>
459concept ExecutionPolicy = std::same_as<std::remove_cvref_t<P>, execution::Sequential>
460 || internal::UserExecutionPolicy<P> || internal::StandardExecutionPolicy<P>;
461
462namespace internal
463{
464
468template <typename P>
469inline constexpr bool isSequential = std::same_as<std::remove_cvref_t<P>, execution::Sequential>;
470
475template <typename... Args>
476inline constexpr bool leadsWithPolicy = false;
477
479template <typename First, typename... Rest>
480inline constexpr bool leadsWithPolicy<First, Rest...> = ExecutionPolicy<First>;
481
487template <typename T, typename P>
488[[nodiscard]] auto
489scratchVector(P const & policy, std::size_t size)
490{
491 if constexpr (UserPolicyWithAllocator<P>)
492 {
493 using Allocator = ReBound<std::remove_cvref_t<decltype(policy.get_allocator())>, T>;
494 return std::vector<T, Allocator>(size, Allocator(policy.get_allocator()));
495 }
496 else
497 {
498 return std::vector<T>(size);
499 }
500}
501
506inline constexpr Eigen::Index chunkSize = 1024;
507
509[[nodiscard]] constexpr Eigen::Index
510chunkCount(Eigen::Index count)
511{
512 return (count + chunkSize - 1) / chunkSize;
513}
514
520template <ExecutionPolicy P, typename F>
521void
522forEachIndex([[maybe_unused]] P && policy, Eigen::Index count, F && f)
523{
524 EMRYS_FILTER_ASSERT(count >= 0);
525 if constexpr (isSequential<P>)
526 {
527 for (Eigen::Index i = 0; i < count; ++i)
528 {
529 f(i);
530 }
531 }
532 else if constexpr (UserExecutionPolicy<P>)
533 {
534 std::as_const(policy).forEachIndex(count, [&f](Eigen::Index i) {
535 f(i);
536 });
537 }
538 else
539 {
540 std::for_each(std::forward<P>(policy), CountingIterator{0}, CountingIterator{count}, [&f](Eigen::Index i) {
541 f(i);
542 });
543 }
544}
545
550template <ExecutionPolicy P, typename F>
551void
552forEachChunk(P && policy, Eigen::Index count, F && f)
553{
554 EMRYS_FILTER_ASSERT(count >= 0);
555 if constexpr (isSequential<P>)
556 {
557 if (count > 0)
558 {
559 f(Eigen::Index{0}, count);
560 }
561 }
562 else
563 {
564 forEachIndex(std::forward<P>(policy), chunkCount(count), [&](Eigen::Index chunk) {
565 Eigen::Index const begin = chunk * chunkSize;
566 f(begin, std::min(chunkSize, count - begin));
567 });
568 }
569}
570
582template <ExecutionPolicy P, typename Partial, typename Combine>
583[[nodiscard]] auto
584reduceRanges(P && policy, Eigen::Index count, Partial const & partial, Combine const & combine)
585{
586 using T = std::remove_cvref_t<std::invoke_result_t<Partial const &, Eigen::Index, Eigen::Index>>;
587 EMRYS_FILTER_ASSERT(count >= 0);
588 if constexpr (isSequential<P>)
589 {
590 return T{partial(Eigen::Index{0}, count)};
591 }
592 else
593 {
594 Eigen::Index const chunks = chunkCount(count);
595 // An empty range, as one chunk, is one call to partial(), as under Sequential.
596 if (chunks <= 1)
597 {
598 return T{partial(Eigen::Index{0}, count)};
599 }
600 auto partials = scratchVector<T>(policy, static_cast<std::size_t>(chunks));
601 forEachChunk(std::forward<P>(policy), count, [&](Eigen::Index begin, Eigen::Index length) {
602 partials[static_cast<std::size_t>(begin / chunkSize)] = partial(begin, length);
603 });
604 T result = std::move(partials[0]);
605 for (std::size_t c = 1; c < partials.size(); ++c)
606 {
607 result = combine(std::move(result), partials[c]);
608 }
609 return result;
610 }
611}
612
617template <ExecutionPolicy P, typename T, typename Combine, typename F>
618[[nodiscard]] T
619transformReduce(P && policy, Eigen::Index count, T const & identity, Combine const & combine, F const & f)
620{
621 return reduceRanges(
622 std::forward<P>(policy),
623 count,
624 [&](Eigen::Index begin, Eigen::Index length) {
625 T result = identity;
626 for (Eigen::Index i = begin; i < begin + length; ++i)
627 {
628 result = combine(std::move(result), T(f(i)));
629 }
630 return result;
631 },
632 combine
633 );
634}
635
645template <ExecutionPolicy P, typename T>
646void
647cumulativeSum(P && policy, std::span<T const> in, std::span<T> out)
648{
649 EMRYS_FILTER_ASSERT(in.size() == out.size() && "the scan writes one sum per element");
650 Eigen::Index const count = Eigen::Index(in.size());
651 // Read each element before writing its sum, so that in and out may be the same.
652 auto const scan = [&](Eigen::Index begin, Eigen::Index length, T const * offset) {
653 auto const first = std::size_t(begin);
654 T sum = offset ? T(*offset + in[first]) : in[first];
655 out[first] = sum;
656 for (std::size_t i = first + 1; i < std::size_t(begin + length); ++i)
657 {
658 sum += in[i];
659 out[i] = sum;
660 }
661 };
662 if (count == 0)
663 {
664 return;
665 }
666 if constexpr (isSequential<P>)
667 {
668 scan(0, count, nullptr);
669 }
670 else
671 {
672 Eigen::Index const chunks = chunkCount(count);
673 if (chunks == 1)
674 {
675 scan(0, count, nullptr);
676 return;
677 }
678 auto offsets = scratchVector<T>(policy, static_cast<std::size_t>(chunks));
679 forEachChunk(policy, count, [&](Eigen::Index begin, Eigen::Index length) {
680 auto const first = std::size_t(begin);
681 T total = in[first];
682 for (std::size_t i = first + 1; i < std::size_t(begin + length); ++i)
683 {
684 total += in[i];
685 }
686 offsets[std::size_t(begin / chunkSize)] = total;
687 });
688 // Chunk c starts from the sum of the chunks before it; chunk 0 starts from nothing.
689 T carry = offsets[0];
690 for (std::size_t c = 1; c < offsets.size(); ++c)
691 {
692 T const total = offsets[c];
693 offsets[c] = carry;
694 carry += total;
695 }
696 forEachChunk(std::forward<P>(policy), count, [&](Eigen::Index begin, Eigen::Index length) {
697 std::size_t const chunk = std::size_t(begin / chunkSize);
698 scan(begin, length, chunk == 0 ? nullptr : &offsets[chunk]);
699 });
700 }
701}
702
706template <ExecutionPolicy P, typename FromDerived, typename To>
707void
708gather(P && policy, std::span<Eigen::Index const> indices, Eigen::MatrixBase<FromDerived> const & from, To && to)
709{
710 EMRYS_FILTER_ASSERT(to.cols() == Eigen::Index(indices.size()) && "gather one column per index");
711 forEachIndex(std::forward<P>(policy), Eigen::Index(indices.size()), [&](Eigen::Index j) {
712 Eigen::Index const source = indices[std::size_t(j)];
713 EMRYS_FILTER_ASSERT(source >= 0 && source < from.cols() && "a gathered index is out of range");
714 to.col(j) = from.col(source);
715 });
716}
717
726template <ExecutionPolicy P, typename Keep, typename Move>
727Eigen::Index
728copyIfIndex(P && policy, Eigen::Index count, Keep const & keep, Move const & move)
729{
730 EMRYS_FILTER_ASSERT(count >= 0);
731 if constexpr (isSequential<P>)
732 {
733 Eigen::Index out = 0;
734 for (Eigen::Index i = 0; i < count; ++i)
735 {
736 if (keep(i))
737 {
738 move(i, out++);
739 }
740 }
741 return out;
742 }
743 else
744 {
745 if (count == 0)
746 {
747 return 0;
748 }
749 auto positions = scratchVector<Eigen::Index>(policy, static_cast<std::size_t>(count));
750 forEachIndex(policy, count, [&](Eigen::Index i) {
751 positions[std::size_t(i)] = keep(i) ? 1 : 0;
752 });
753 cumulativeSum(policy, std::span<Eigen::Index const>{positions}, std::span<Eigen::Index>{positions});
754 forEachIndex(std::forward<P>(policy), count, [&](Eigen::Index i) {
755 Eigen::Index const before = i == 0 ? 0 : positions[std::size_t(i - 1)];
756 if (positions[std::size_t(i)] != before)
757 {
758 move(i, before);
759 }
760 });
761 return positions.back();
762 }
763}
764
766template <ExecutionPolicy P, typename T>
767void
768fill(P && policy, std::span<T> out, T const & value)
769{
770 forEachChunk(std::forward<P>(policy), Eigen::Index(out.size()), [&](Eigen::Index begin, Eigen::Index length) {
771 std::fill_n(out.begin() + begin, length, value);
772 });
773}
774
775} // namespace internal
776
777} // namespace Emrys
The allocator conventions of decisions.md [allocation]: DefaultAllocator, Capacity,...
The macros every header includes first.
An execution policy: execution::sequential, a policy of the standard library such as std::execution::...
Definition Execution.h:459
constexpr Sequential sequential
The default execution policy: plain loops on the calling thread, in index order (Sequential).
Definition Execution.h:128
The library's execution policies: the constant execution::sequential and its type.
Definition Execution.h:115
The type of execution::sequential, the default execution policy: plain loops on the calling thread,...
Definition Execution.h:123