BioCMAMC-ST
particles_container.hpp
1#ifndef __PARTICLES_CONTAINER_HPP__
2#define __PARTICLES_CONTAINER_HPP__
3
4#include "Kokkos_Macros.hpp"
5#include <Kokkos_Core.hpp>
6#include <biocma_cst_config.hpp>
7#include <cmath>
8#include <common/common.hpp>
9#include <common/env_var.hpp>
10#include <common/execinfo.hpp>
11#include <common/has_serialize.hpp>
12#include <cstdint>
13#include <mc/alias.hpp>
14#include <mc/first_touch.hpp>
15#include <mc/prng/prng.hpp>
16#include <mc/traits.hpp>
17
18#include <Kokkos_Sort.hpp>
19#include <sorting/Kokkos_BinSortPublicAPI.hpp>
20#include <sorting/impl/Kokkos_CopyOpsForBinSortImpl.hpp>
21#include <sorting/impl/Kokkos_SortByKeyImpl.hpp>
22#include <stdexcept>
23#include <utility>
24namespace MC
25{
26
28 {
30 double buffer_ratio{};
31 double allocation_factor = {};
32 double shrink_ratio{};
34
35 template <class Archive>
36 void
45 };
46
58 template <ModelType Model> class ParticlesContainer
59 {
60 public:
61 using UsedModel = Model;
62
67 std::size_t n_particle,
68 KernelDispatchOptions kernel_opts = {});
69 ParticlesContainer(); //=default;
77
82
83 // NOLINTBEGIN(cppcoreguidelines-non-private-member-variables-in-classes)
84 Model::SelfParticle model;
85 Model::SelfContribs contribs;
86
91 // NOLINTEND(cppcoreguidelines-non-private-member-variables-in-classes)
92
97 // template <typename CviewType>
98 // KOKKOS_INLINE_FUNCTION void
99 // get_contributions(const std::size_t idx,
100 // const CviewType& contributions) const
101 // {
102 // if (begin < end)
103 // {
104 // static_assert(ConstWeightModelType<Model>,
105 // "ModelType: Constapply_weight()");
106
107 // const double weight = get_weight(idx);
108 // const auto pos = position(idx);
109 // auto access = contributions.access();
110
111 // for (int i = begin; i < end; ++i)
112 // {
113 // const int rel = i - begin;
114 // access(rel, pos) += weight * model(idx, i);
115 // }
116 // }
117 // }
118
132 [[nodiscard]] KOKKOS_INLINE_FUNCTION bool
133 handle_division(const MC::pool_type& random_pool, std::size_t idx1) const;
134
138 [[nodiscard]] KOKKOS_INLINE_FUNCTION Model::FloatType
139 get_weight(std::size_t idx) const;
140
141 // HOST
142
148
153
157 template <class Archive> void save(Archive& ar) const;
158
162 template <class Archive> void load(Archive& ar);
163
167 [[nodiscard]] double get_allocation_factor() const noexcept;
168
173 [[nodiscard]] std::size_t capacity() const noexcept;
174
178 [[nodiscard]] std::size_t get_inactive() const noexcept;
179
185 void update_and_remove_inactive(std::size_t out, std::size_t dead);
186
193 [[nodiscard]] KOKKOS_INLINE_FUNCTION std::size_t n_particles() const;
194
195 void change_runtime(RuntimeParameters&& parameters) noexcept;
196
197 void _sort(size_t n_c);
198
216 void remove_inactive_particles(std::size_t to_remove);
217
218// FIXME: used only in unit test
219#ifndef NDEBUG
220 [[maybe_unused]] [[nodiscard]] auto get_buffer_index() const;
221#endif
222
223 private:
224 Model::SelfParticle buffer_model;
226 Kokkos::View<uint64_t, Kokkos::SharedSpace> buffer_index;
229 std::size_t inactive_counter;
233
235 void _resize(std::size_t new_size, bool force = false);
237 // FIXME
238 public:
239 // int begin;
240 // int end;
241
242 // int begin;
243 // int end;
244 };
245
246} // namespace MC
247
248/*
249 * IMPLEMENTATION
250 */
251namespace MC
252{
253 namespace
254 {
255
256 using TeamPolicy = Kokkos::TeamPolicy<ComputeSpace>;
257 using TeamMember = TeamPolicy::member_type;
258
297 template <ModelType M> struct CompactParticlesFunctor
298 {
299
300 CompactParticlesFunctor(MC::ParticleStatus _status,
301 M::SelfParticle _model,
302 M::SelfContribs _contribs,
303 MC::ParticlePositions _position,
304 MC::ParticleAges _ages,
305 std::size_t _to_remove,
306 std::size_t _last_used_index)
307 : status(std::move(_status)), model(std::move(_model)),
308 contribs(std::move(_contribs)), position(std::move(_position)),
309 ages(std::move(_ages)), offset("offset"), to_remove(_to_remove),
310 last_used_index(_last_used_index)
311 {
312
313 Kokkos::deep_copy(offset, 0);
314 }
315
316 KOKKOS_INLINE_FUNCTION void
317 operator()(const int i, std::size_t& update, const bool final) const
318 {
319
326
327 // Check if the particle is non-idle.
328 const bool is_inactive = (status(i) != MC::Status::Idle);
329
330 // Capture the current prefix sum of inactive particles.
331 const std::size_t scan_index = update;
332
333 // Increment the shared counter if the particle is inactive.
334 update += is_inactive ? 1 : 0;
335
336 // Final pass: Compact the container by replacing inactive particles.
337 if (final && is_inactive && scan_index < to_remove)
338 {
339
340 const auto inactive_slot = i; // Index of the "gap" to fill
341
342 // Atomically find a replacement particle from the end of the
343 // container. The replacement must be alive (idle) and not the same as
344 // the particle being removed.
345 // Index of the particle that will "fill" the inactive slot
346 auto replacement_index
347 = last_used_index - Kokkos::atomic_fetch_add(&offset(), 1);
348 while (status(replacement_index) != MC::Status::Idle
349 || replacement_index
350 == static_cast<std::size_t>(inactive_slot))
351 {
352 replacement_index
353 = last_used_index - Kokkos::atomic_fetch_add(&offset(), 1);
354 }
355
356 // Mark the removed particle's position as idle.
357 status(inactive_slot) = MC::Status::Idle;
358
359 // Merge position EZ
360 // No need atomic: final executed exactly once
361 Kokkos::atomic_exchange(&position(inactive_slot),
362 position(replacement_index));
363
364 // TODO Use hierachical parallism here, thread range is likely to work
365 for (std::size_t i_properties = 0; i_properties < M::n_var;
366 ++i_properties)
367 {
368 model(inactive_slot, i_properties)
369 = model(replacement_index, i_properties);
370 }
371
372 for (std::size_t i_c = 0; i_c < M::n_c; ++i_c)
373 {
374 contribs(inactive_slot, i_c) = contribs(replacement_index, i_c);
375 }
376
377 ages(inactive_slot, 0) = ages(replacement_index, 0);
378 ages(inactive_slot, 1) = ages(replacement_index, 1);
379 }
380 }
381
382 MC::ParticleStatus status;
383 M::SelfParticle model;
384 M::SelfContribs contribs;
385 MC::ParticlePositions position;
386 MC::ParticleAges ages;
387 Kokkos::View<std::size_t, ComputeSpace> offset;
388 std::size_t to_remove;
389 std::size_t last_used_index;
390 };
391
400 template <ModelType M> struct InsertFunctor
401 {
402 InsertFunctor(std::size_t _original_size,
403 M::SelfParticle _model,
404 MC::ParticlePositions _position,
405 MC::ParticleAges _ages,
406 M::SelfParticle _buffer_model,
407 MC::ParticlePositions _buffer_position)
408 : original_size(_original_size), model(std::move(_model)),
409 ages(std::move(_ages)), position(std::move(_position)),
410 buffer_model(std::move(_buffer_model)),
411 buffer_position(std::move(_buffer_position))
412 {
413 }
414 struct TagRow
415 {
416 };
417 struct TagScalar
418 {
419 };
420
421 KOKKOS_INLINE_FUNCTION
422 void
423 operator()(TagRow, const std::size_t i, const std::size_t j) const
424 {
425 model(original_size + i, j) = buffer_model(i, j);
426 }
427
428 KOKKOS_INLINE_FUNCTION
429 void
430 operator()(TagScalar, const std::size_t i) const
431 {
432 position(original_size + i) = buffer_position(i);
433
434 // Actually needs buffer to store mother's hydraulic time
435 // But set new hydraulic time to 0 to not create new buffer a save
436 // memory usage
437 ages(original_size + i, 0) = 0;
438 ages(original_size + i, 1) = 0;
439 }
440
441 std::size_t original_size;
442 M::SelfParticle model;
443 MC::ParticleAges ages;
444 MC::ParticlePositions position;
445 M::SelfParticle buffer_model;
446 MC::ParticlePositions buffer_position;
447 };
448
449 }; // namespace
450
451 template <ModelType Model>
452 [[nodiscard]] KOKKOS_INLINE_FUNCTION std::size_t
457
458 template <ModelType Model>
459 [[nodiscard]] std::size_t
461 {
462 return inactive_counter;
463 }
464
465 template <ModelType Model>
466 void
471#ifndef NDEBUG
472
473 template <ModelType Model>
474 [[maybe_unused]] [[nodiscard]] auto
479#endif
480
481 template <ModelType Model>
482 [[nodiscard]] double
484 {
485 return rt_params.allocation_factor;
486 }
487
488 template <ModelType Model>
489 [[nodiscard]] std::size_t
491 {
493 }
494
495 template <ModelType Model>
496 template <class Archive>
497 void
499 {
500 // Basically store everything that is usefull
502
503 // Allocation is done in the deserialize
504 deserialize_view(ar, weights);
505 deserialize_view(ar, position);
506 deserialize_view(ar, status);
507 deserialize_view(ar, model);
508 deserialize_view(ar, ages);
509
510 if (Model::n_var != model.extent(1))
511 {
512 throw std::runtime_error(
513 "Error when deserialze, model number of property mismatch");
514 }
515
516 // contribs is not serialized and is rewritten every step: allocate, don't
517 // copy
518 Kokkos::realloc(this->contribs, n_allocated_elements, Model::n_c);
519#ifndef NDEBUG
520 Kokkos::printf("ParticlesContainer::load: Check if load_tuning_constant "
521 "works with different value");
522#endif
523
524 __allocate_buffer__(); // Dont forget to allocate buffer
525 }
526
527 template <ModelType Model>
528 template <class Archive>
529 void
531 {
532
533 // Basically store everything that is usefull
535 serialize_view(ar, weights);
536 serialize_view(ar, position);
537 serialize_view(ar, status);
538 serialize_view(ar, model);
539 serialize_view(ar, ages);
540 }
541
542 template <ModelType Model>
543 void
545 const std::size_t dead)
546 {
547 // Actually out particle and dead ones are just inactive
548 inactive_counter += out;
549 inactive_counter += dead;
550
551 const auto _threshold = std::max(
552 rt_params.minimum_dead_particle_removal,
553 static_cast<uint64_t>(static_cast<double>(n_used_elements)
554 * rt_params.dead_particle_ratio_threshold));
555
556 if (inactive_counter > _threshold)
557 {
559 }
560 }
561
562 template <ModelType Model>
563 KOKKOS_INLINE_FUNCTION bool
565 std::size_t idx1) const
566 {
567 if (Kokkos::atomic_load(&buffer_index()) < buffer_model.extent(0))
568 {
569 const auto idx2 = Kokkos::atomic_fetch_add(&buffer_index(), 1);
570 Model::division(random_pool, idx1, idx2, model, buffer_model);
571 buffer_position(idx2) = position(idx1);
572 ages(idx1, 1) = 0;
573 return true;
574 }
575 return false;
576 }
577
578 template <ModelType Model>
579 void
581 {
582 PROFILE_SECTION("ParticlesContainer::merge_buffer")
583 const auto original_size = n_used_elements;
584 const auto n_add_item = buffer_index();
585 if (n_add_item == 0)
586 {
587 return;
588 }
589 _resize(original_size + n_add_item);
590
591 using functor_type = InsertFunctor<Model>;
592 const functor_type functor(
593 original_size, model, position, ages, buffer_model, buffer_position);
594
595 Kokkos::parallel_for("insert_merge_rows",
596 Kokkos::MDRangePolicy<typename functor_type::TagRow,
598 Kokkos::Rank<2>>(
599 { 0, 0 },
600 { static_cast<int64_t>(n_add_item),
601 static_cast<int64_t>(Model::n_var) }),
602 functor);
603
604 Kokkos::parallel_for(
605 "insert_merge_scalars",
606 Kokkos::RangePolicy<typename functor_type::TagScalar, ComputeSpace>(
607 0, n_add_item),
608 functor);
609
610 buffer_index() = 0;
611 n_used_elements += n_add_item;
613 }
614
615 template <ModelType Model>
616 void
617 ParticlesContainer<Model>::_resize(std::size_t new_size, bool force)
618 {
619 PROFILE_SECTION("ParticlesContainer::_resize")
620
621 // Ensure new_size is greater than zero
622 if (new_size > 0)
623 {
624 // Determine if resizing is necessary based on the condition
625 if (new_size > n_allocated_elements || force)
626 {
627 // Calculate the new allocated size
628 const auto new_allocated_size = static_cast<std::size_t>(std::ceil(
629 static_cast<double>(new_size) * rt_params.allocation_factor));
630
631 // Update the allocated size
632 n_allocated_elements = new_allocated_size;
633
634 // Perform the resizing on all relevant data containers.
635 // resize_first_touch, not Kokkos::resize: same result, but the pages
636 // of the live range [0, new_size) for correct first touch
637 const auto npt = kernel_options.m_p_p_team_move;
640 model, n_allocated_elements, new_size, npt, Model::n_var);
641 // contribs is rewritten every step before it is read, so its previous
642 // contents do not need preserving -- only its placement matters.
644 contribs, n_allocated_elements, new_size, npt, Model::n_c);
647
648 // Handle resizing for weights based on model type
649 if constexpr (ConstWeightModelType<Model>)
650 {
651 Kokkos::resize(weights,
652 1); // Fixed size for ConstWeightModelType
653 }
654 else
655 {
656 Kokkos::resize(weights, n_allocated_elements);
657 }
658 }
659 }
660 }
661
662 // template <ModelType Model>
663 // void
664 // ParticlesContainer<Model>::__allocate_buffer__()
665 // {
666 // PROFILE_SECTION("ParticlesContainer::__allocate_buffer__")
667
668 // std::size_t buffer_size = buffer_position.extent(0);
669
670 // const double tmp = rt_params.buffer_ratio * n_allocated_elements;
671
672 // const std::size_t buffer_threshold = static_cast<std::size_t>(tmp);
673
674 // if (buffer_size < buffer_threshold)
675 // {
676 // buffer_size = static_cast<std::size_t>(
677 // std::ceil(rt_params.buffer_ratio * n_allocated_elements));
678
679 // // Realloc because not needed to keep buffer as it has been copied
680 // Kokkos::realloc(buffer_position, buffer_size);
681 // Kokkos::realloc(buffer_model, buffer_size, Model::n_var);
682 // buffer_index() = 0;
683 // }
684 // }
685
686 template <ModelType Model>
687 void
689 {
690 PROFILE_SECTION("ParticlesContainer::__allocate_buffer__")
691
692 const auto required_buffer_size = static_cast<std::size_t>(
693 std::ceil(rt_params.buffer_ratio * n_allocated_elements));
694
695 if (buffer_position.extent(0) < required_buffer_size)
696 {
697 // Realloc because not needed to keep buffer as it has been copied.
698 // The buffer is written by whichever team creates the new particle, so
699 // touch it the same way the container itself is touched.
700 const auto npt = kernel_options.m_p_p_team_move;
702 buffer_position, required_buffer_size, required_buffer_size, npt);
704 required_buffer_size,
705 required_buffer_size,
706 npt,
707 Model::n_var);
708 buffer_index() = 0;
709 }
710 }
711
712// NOLINTBEGIN
713#define alloc_without_init(name) \
714 Kokkos::view_alloc(Kokkos::WithoutInitializing, name)
715 // NOLINTEND
716
717 template <ModelType M>
719 std::size_t n_particle,
720 KernelDispatchOptions kernel_opts)
721 : model(alloc_without_init("particle_model"), 0, 0),
722
723 contribs(alloc_without_init("particle_contribs"), 0),
724 position(alloc_without_init("particle_position"), 0),
725 status(alloc_without_init("particle_status"), 0),
726 weights(alloc_without_init("particle_weigth"), 0),
727 ages(alloc_without_init("particle_age"), 0),
728 buffer_model("buffer_particle_model", 0),
729 buffer_position("buffer_particle_position", 0),
730 buffer_index("buffer_index"), n_allocated_elements(0),
731 n_used_elements(n_particle), inactive_counter(0),
732 kernel_options(kernel_opts), rt_params(rt_param)
733 {
734
735 // load_tuning_constant();
736
737 if (n_particle != 0)
738 {
739 _resize(n_particle);
741 }
742
743 // const auto bounds = M::get_bounds();
744
745 // if (begin > end)
746 // {
747 // throw std::invalid_argument("Model begin should be > end");
748 // }
749
750 // begin = bounds.begin;
751 // end = bounds.end;
752 }
753
754 template <ModelType M>
759
760 template <ModelType M>
761 void
763 {
764
765 PROFILE_SECTION("ParticlesContainer::remove_inactive_particles")
766 if (to_remove == 0)
767 {
768 return;
769 }
770
771 if (to_remove == n_used_elements)
772 {
773 _resize(0, true);
774 n_used_elements = 0;
776 }
777 else if (to_remove > n_used_elements)
778 {
779 throw std::runtime_error(
780 "remove_inactive_particles: Error in kernel cannot remove more "
781 "element than existing");
782 }
783 else
784 {
785
786 const auto new_used_item = n_used_elements - to_remove;
787
788 const auto last_used_index = n_used_elements - 1;
789 Kokkos::parallel_scan(
790 "find_and_fill_gap",
791 Kokkos::RangePolicy<ComputeSpace>(0, n_used_elements),
792 CompactParticlesFunctor<M>(status,
793 model,
794 contribs,
795 position,
796 ages,
797 to_remove,
798 last_used_index));
799
800 Kokkos::fence();
801 KOKKOS_ASSERT(this->position.extent(0) == n_allocated_elements);
802 KOKKOS_ASSERT(this->contribs.extent(0) == n_allocated_elements);
803 KOKKOS_ASSERT(this->model.extent(0) == n_allocated_elements);
804 KOKKOS_ASSERT(this->status.extent(0) == n_allocated_elements);
805
806 n_used_elements = new_used_item;
807 const bool do_shrink = n_used_elements <= static_cast<std::size_t>(
808 rt_params.shrink_ratio * n_allocated_elements);
809
810 if (do_shrink)
811 {
812 // force to true if we want to shrink
813 _resize(n_used_elements * rt_params.allocation_factor, true);
814 }
816 };
817 }
818
819 template <ModelType M>
820 [[nodiscard]] KOKKOS_INLINE_FUNCTION M::FloatType
821 ParticlesContainer<M>::get_weight(const std::size_t idx) const
822 {
823 if constexpr (ConstWeightModelType<M>)
824 {
825 return weights(0);
826 }
827 else
828 {
829 return weights(idx);
830 }
831 }
832
833 template <ModelType M>
834 void
836 {
837 this->rt_params = parameters;
838 }
839
840 template <ModelType M>
841 void
843 {
844 (void)n_c;
845
846 // PROFILE_SECTION("SORT")
847 // // const auto N = n_used_elements;
848 const int bin_size = 2048;
849
850 const auto sn
851 = Kokkos::subview(position, std::pair<int, int>(0, n_used_elements));
852
853 using ExecSpace = Kokkos::DefaultExecutionSpace;
854 using view_type = decltype(position);
855 auto binop = Kokkos::BinOp1D<view_type>(bin_size, 0, n_c);
856
857 auto sorter = Kokkos::BinSort<view_type, decltype(binop)>(
858 ExecSpace(), sn, binop, true);
859 }
860
861} // namespace MC
862
863#endif
RuntimeParameters rt_params
Definition particles_container.hpp:236
void update_and_remove_inactive(std::size_t out, std::size_t dead)
Definition particles_container.hpp:544
double get_allocation_factor() const noexcept
Get the allocation factor.
Definition particles_container.hpp:483
Model::SelfContribs contribs
Definition particles_container.hpp:85
~ParticlesContainer()=default
Default destructor.
void force_remove_dead()
Clean all the non-idle particle even if number smaller than threshold.
Definition particles_container.hpp:467
Model::SelfParticle model
Definition particles_container.hpp:84
KOKKOS_INLINE_FUNCTION std::size_t n_particles() const
Definition particles_container.hpp:453
KOKKOS_INLINE_FUNCTION bool handle_division(const MC::pool_type &random_pool, std::size_t idx1) const
Get the contribution if particle at index idx.
Definition particles_container.hpp:564
KernelDispatchOptions kernel_options
Definition particles_container.hpp:232
Kokkos::View< uint64_t, Kokkos::SharedSpace > buffer_index
Definition particles_container.hpp:226
void _resize(std::size_t new_size, bool force=false)
Definition particles_container.hpp:617
void remove_inactive_particles(std::size_t to_remove)
Definition particles_container.hpp:762
void __allocate_buffer__()
Definition particles_container.hpp:688
ParticlesContainer()
Definition particles_container.hpp:755
ParticlesContainer & operator=(ParticlesContainer &&)=default
std::size_t get_inactive() const noexcept
Definition particles_container.hpp:460
std::size_t capacity() const noexcept
Definition particles_container.hpp:490
uint64_t n_used_elements
Definition particles_container.hpp:228
auto get_buffer_index() const
Definition particles_container.hpp:475
KOKKOS_INLINE_FUNCTION Model::FloatType get_weight(std::size_t idx) const
Return the particle weight.
Definition particles_container.hpp:821
void merge_buffer()
Insert particle buffer into the main container.
Definition particles_container.hpp:580
Model::SelfParticle buffer_model
Definition particles_container.hpp:224
ParticlePositions buffer_position
Definition particles_container.hpp:225
MC::ParticlePositions position
Definition particles_container.hpp:87
void save(Archive &ar) const
Save data into ar for serialization.
Definition particles_container.hpp:530
std::size_t inactive_counter
Definition particles_container.hpp:229
ParticleAges ages
Definition particles_container.hpp:90
Model UsedModel
Definition particles_container.hpp:61
ParticleWeigths< typename Model::FloatType > weights
Definition particles_container.hpp:89
ParticlesContainer & operator=(const ParticlesContainer &)=default
void _sort(size_t n_c)
Definition particles_container.hpp:842
ParticlesContainer(const ParticlesContainer &)=default
Default copy and move constructors and assignment operators.
void load(Archive &ar)
Load data from ar for deserialization.
Definition particles_container.hpp:498
ParticlesContainer(RuntimeParameters rt_param, std::size_t n_particle, KernelDispatchOptions kernel_opts={})
Alias for the model used by the container.
Definition particles_container.hpp:718
ParticlesContainer(ParticlesContainer &&)=default
std::size_t n_allocated_elements
Definition particles_container.hpp:227
void change_runtime(RuntimeParameters &&parameters) noexcept
Definition particles_container.hpp:835
MC::ParticleStatus status
Definition particles_container.hpp:88
Concept to check if a model type has uniform_weight
Definition traits.hpp:203
Namespace that contains classes and structures related to Monte Carlo (MC) simulations.
Definition alias.hpp:16
Kokkos::View< Status *, ComputeSpace > ParticleStatus
Definition alias.hpp:141
Kokkos::View< uint64_t *, ComputeSpace > ParticlePositions
Definition alias.hpp:140
@ Idle
Definition alias.hpp:126
ParticleAgesBase< ComputeSpace > ParticleAges
Definition alias.hpp:144
gen_pool_type< Kokkos::DefaultExecutionSpace > pool_type
Definition alias.hpp:100
Kokkos::View< ftype *, ComputeSpace > ParticleWeigths
Definition alias.hpp:143
Kokkos::DefaultExecutionSpace ComputeSpace
Definition alias.hpp:17
void resize_first_touch(ViewType &view, std::size_t n_alloc, std::size_t n_live, std::size_t npt, Extents... rest)
Kokkos::resize, but with the first touch placed by us.
Definition first_touch.hpp:106
Definition execinfo.hpp:13
Definition particles_container.hpp:28
void serialize(Archive &ar)
Definition particles_container.hpp:37
double buffer_ratio
Definition particles_container.hpp:30
double dead_particle_ratio_threshold
Definition particles_container.hpp:33
uint64_t minimum_dead_particle_removal
Definition particles_container.hpp:29
double shrink_ratio
Definition particles_container.hpp:32
double allocation_factor
Definition particles_container.hpp:31