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/has_serialize.hpp>
11#include <cstdint>
12#include <mc/alias.hpp>
13#include <mc/prng/prng.hpp>
14#include <mc/traits.hpp>
15
16#include <Kokkos_Sort.hpp>
17#include <sorting/Kokkos_BinSortPublicAPI.hpp>
18#include <sorting/impl/Kokkos_CopyOpsForBinSortImpl.hpp>
19#include <sorting/impl/Kokkos_SortByKeyImpl.hpp>
20#include <stdexcept>
21#include <utility>
22namespace MC
23{
24
26 {
28 double buffer_ratio{};
29 double allocation_factor = {};
30 double shrink_ratio{};
32
33 template <class Archive>
34 void
43 };
44
56 template <ModelType Model> class ParticlesContainer
57 {
58 public:
59 using UsedModel = Model;
60
65 std::size_t n_particle);
66 ParticlesContainer(); //=default;
74
79
80 // NOLINTBEGIN(cppcoreguidelines-non-private-member-variables-in-classes)
81 Model::SelfParticle model;
82 Model::SelfContribs contribs;
83
88 // NOLINTEND(cppcoreguidelines-non-private-member-variables-in-classes)
89
94 // template <typename CviewType>
95 // KOKKOS_INLINE_FUNCTION void
96 // get_contributions(const std::size_t idx,
97 // const CviewType& contributions) const
98 // {
99 // if (begin < end)
100 // {
101 // static_assert(ConstWeightModelType<Model>,
102 // "ModelType: Constapply_weight()");
103
104 // const double weight = get_weight(idx);
105 // const auto pos = position(idx);
106 // auto access = contributions.access();
107
108 // for (int i = begin; i < end; ++i)
109 // {
110 // const int rel = i - begin;
111 // access(rel, pos) += weight * model(idx, i);
112 // }
113 // }
114 // }
115
129 [[nodiscard]] KOKKOS_INLINE_FUNCTION bool
130 handle_division(const MC::pool_type& random_pool, std::size_t idx1) const;
131
135 [[nodiscard]] KOKKOS_INLINE_FUNCTION Model::FloatType
136 get_weight(std::size_t idx) const;
137
138 // HOST
139
145
150
154 template <class Archive> void save(Archive& ar) const;
155
159 template <class Archive> void load(Archive& ar);
160
164 [[nodiscard]] double get_allocation_factor() const noexcept;
165
170 [[nodiscard]] std::size_t capacity() const noexcept;
171
175 [[nodiscard]] std::size_t get_inactive() const noexcept;
176
182 void update_and_remove_inactive(std::size_t out, std::size_t dead);
183
190 [[nodiscard]] KOKKOS_INLINE_FUNCTION std::size_t n_particles() const;
191
192 void change_runtime(RuntimeParameters&& parameters) noexcept;
193
194 void _sort(size_t n_c);
195
213 void remove_inactive_particles(std::size_t to_remove);
214
215// FIXME: used only in unit test
216#ifndef NDEBUG
217 [[maybe_unused]] [[nodiscard]] auto get_buffer_index() const;
218#endif
219
220 private:
221 Model::SelfParticle buffer_model;
223 Kokkos::View<uint64_t, Kokkos::SharedSpace> buffer_index;
226 std::size_t inactive_counter;
227
229 void _resize(std::size_t new_size, bool force = false);
231 // FIXME
232 public:
233 // int begin;
234 // int end;
235
236 // int begin;
237 // int end;
238 };
239
240} // namespace MC
241
242/*
243 * IMPLEMENTATION
244 */
245namespace MC
246{
247 namespace
248 {
249
250 using TeamPolicy = Kokkos::TeamPolicy<ComputeSpace>;
251 using TeamMember = TeamPolicy::member_type;
252
291 template <ModelType M> struct CompactParticlesFunctor
292 {
293
294 CompactParticlesFunctor(MC::ParticleStatus _status,
295 M::SelfParticle _model,
296 M::SelfContribs _contribs,
297 MC::ParticlePositions _position,
298 MC::ParticleAges _ages,
299 std::size_t _to_remove,
300 std::size_t _last_used_index)
301 : status(std::move(_status)), model(std::move(_model)),
302 contribs(std::move(_contribs)), position(std::move(_position)),
303 ages(std::move(_ages)), offset("offset"), to_remove(_to_remove),
304 last_used_index(_last_used_index)
305 {
306
307 Kokkos::deep_copy(offset, 0);
308 }
309
310 KOKKOS_INLINE_FUNCTION void
311 operator()(const int i, std::size_t& update, const bool final) const
312 {
313
320
321 // Check if the particle is non-idle.
322 const bool is_inactive = (status(i) != MC::Status::Idle);
323
324 // Capture the current prefix sum of inactive particles.
325 const std::size_t scan_index = update;
326
327 // Increment the shared counter if the particle is inactive.
328 update += is_inactive ? 1 : 0;
329
330 // Final pass: Compact the container by replacing inactive particles.
331 if (final && is_inactive && scan_index < to_remove)
332 {
333
334 const auto inactive_slot = i; // Index of the "gap" to fill
335
336 // Atomically find a replacement particle from the end of the
337 // container. The replacement must be alive (idle) and not the same as
338 // the particle being removed.
339 // Index of the particle that will "fill" the inactive slot
340 auto replacement_index
341 = last_used_index - Kokkos::atomic_fetch_add(&offset(), 1);
342 while (status(replacement_index) != MC::Status::Idle
343 || replacement_index
344 == static_cast<std::size_t>(inactive_slot))
345 {
346 replacement_index
347 = last_used_index - Kokkos::atomic_fetch_add(&offset(), 1);
348 }
349
350 // Mark the removed particle's position as idle.
351 status(inactive_slot) = MC::Status::Idle;
352
353 // Merge position EZ
354 // No need atomic: final executed exactly once
355 Kokkos::atomic_exchange(&position(inactive_slot),
356 position(replacement_index));
357
358 // TODO Use hierachical parallism here, thread range is likely to work
359 for (std::size_t i_properties = 0; i_properties < M::n_var;
360 ++i_properties)
361 {
362 model(inactive_slot, i_properties)
363 = model(replacement_index, i_properties);
364 }
365
366 for (std::size_t i_c = 0; i_c < M::n_c; ++i_c)
367 {
368 contribs(inactive_slot, i_c) = contribs(replacement_index, i_c);
369 }
370
371 ages(inactive_slot, 0) = ages(replacement_index, 0);
372 ages(inactive_slot, 1) = ages(replacement_index, 1);
373 }
374 }
375
376 MC::ParticleStatus status;
377 M::SelfParticle model;
378 M::SelfContribs contribs;
379 MC::ParticlePositions position;
380 MC::ParticleAges ages;
381 Kokkos::View<std::size_t, ComputeSpace> offset;
382 std::size_t to_remove;
383 std::size_t last_used_index;
384 };
385
402 template <ModelType M> struct InsertFunctor
403 {
404 InsertFunctor(std::size_t _original_size,
405 M::SelfParticle _model,
406 MC::ParticlePositions _position,
407 MC::ParticleAges _ages,
408 M::SelfParticle _buffer_model,
409 MC::ParticlePositions _buffer_position)
410 : original_size(_original_size), model(std::move(_model)),
411 ages(std::move(_ages)), position(std::move(_position)),
412 buffer_model(std::move(_buffer_model)),
413 buffer_position(std::move(_buffer_position))
414 {
415 }
416 KOKKOS_INLINE_FUNCTION
417 void
418 operator()(const TeamMember& team) const
419 {
420 auto range = M::n_var;
421 const int i = team.league_rank();
422
423 Kokkos::parallel_for(
424 Kokkos::TeamVectorRange(team, range),
425 [&](const int& j)
426 { model(original_size + i, j) = buffer_model(i, j); });
427 position(original_size + i) = buffer_position(i);
428
429 // Actually needs buffer to store mother's hydraulic time
430 // But set new hydraulic time to 0 to not create new buffer a save
431 // memory usage
432 ages(original_size + i, 0) = 0;
433 ages(original_size + i, 1) = 0;
434 }
435
436 std::size_t original_size;
437 M::SelfParticle model;
438 MC::ParticleAges ages;
439 MC::ParticlePositions position;
440 M::SelfParticle buffer_model;
441 MC::ParticlePositions buffer_position;
442 };
443
444 }; // namespace
445
446 template <ModelType Model>
447 [[nodiscard]] KOKKOS_INLINE_FUNCTION std::size_t
452
453 template <ModelType Model>
454 [[nodiscard]] std::size_t
456 {
457 return inactive_counter;
458 }
459
460 template <ModelType Model>
461 void
466#ifndef NDEBUG
467
468 template <ModelType Model>
469 [[maybe_unused]] [[nodiscard]] auto
474#endif
475
476 template <ModelType Model>
477 [[nodiscard]] double
479 {
480 return rt_params.allocation_factor;
481 }
482
483 template <ModelType Model>
484 [[nodiscard]] std::size_t
486 {
488 }
489
490 template <ModelType Model>
491 template <class Archive>
492 void
494 {
495 // Basically store everything that is usefull
497
498 // Allocation is done in the deserialize
499 deserialize_view(ar, weights);
500 deserialize_view(ar, position);
501 deserialize_view(ar, status);
502 deserialize_view(ar, model);
503 deserialize_view(ar, ages);
504
505 if (Model::n_var != model.extent(1))
506 {
507 throw std::runtime_error(
508 "Error when deserialze, model number of property mismatch");
509 }
510
511 Kokkos::resize(
512 this->contribs,
514 Model::n_c); // Dont forget to allocate contribs which is not saved yet
515#ifndef NDEBUG
516 Kokkos::printf("ParticlesContainer::load: Check if load_tuning_constant "
517 "works with different value");
518#endif
519
520 __allocate_buffer__(); // Dont forget to allocate buffer
521 }
522
523 template <ModelType Model>
524 template <class Archive>
525 void
527 {
528
529 // Basically store everything that is usefull
531 serialize_view(ar, weights);
532 serialize_view(ar, position);
533 serialize_view(ar, status);
534 serialize_view(ar, model);
535 serialize_view(ar, ages);
536 }
537
538 template <ModelType Model>
539 void
541 const std::size_t dead)
542 {
543 // Actually out particle and dead ones are just inactive
544 inactive_counter += out;
545 inactive_counter += dead;
546
547 const auto _threshold = std::max(
548 rt_params.minimum_dead_particle_removal,
549 static_cast<uint64_t>(static_cast<double>(n_used_elements)
550 * rt_params.dead_particle_ratio_threshold));
551
552 if (inactive_counter > _threshold)
553 {
555 }
556 }
557
558 template <ModelType Model>
559 KOKKOS_INLINE_FUNCTION bool
561 std::size_t idx1) const
562 {
563 if (Kokkos::atomic_load(&buffer_index()) < buffer_model.extent(0))
564 {
565 const auto idx2 = Kokkos::atomic_fetch_add(&buffer_index(), 1);
566 Model::division(random_pool, idx1, idx2, model, buffer_model);
567 buffer_position(idx2) = position(idx1);
568 ages(idx1, 1) = 0;
569 return true;
570 }
571 return false;
572 }
573
574 template <ModelType Model>
575 void
577 {
578 PROFILE_SECTION("ParticlesContainer::merge_buffer")
579 const auto original_size = n_used_elements;
580 const auto n_add_item = buffer_index();
581 if (n_add_item == 0)
582 {
583 return;
584 }
585 _resize(original_size + n_add_item);
586 Kokkos::parallel_for("insert_merge",
587 TeamPolicy(n_add_item, Kokkos::AUTO, Model::n_var),
588 InsertFunctor<Model>(original_size,
589 model,
590 position,
591 ages,
594
595 buffer_index() = 0;
596 n_used_elements += n_add_item;
598 }
599
600 template <ModelType Model>
601 void
602 ParticlesContainer<Model>::_resize(std::size_t new_size, bool force)
603 {
604 PROFILE_SECTION("ParticlesContainer::_resize")
605
606 // Ensure new_size is greater than zero
607 if (new_size > 0)
608 {
609 // Determine if resizing is necessary based on the condition
610 if (new_size > n_allocated_elements || force)
611 {
612 // Calculate the new allocated size
613 const auto new_allocated_size = static_cast<std::size_t>(std::ceil(
614 static_cast<double>(new_size) * rt_params.allocation_factor));
615
616 // Update the allocated size
617 n_allocated_elements = new_allocated_size;
618
619 // Perform the resizing on all relevant data containers
620 Kokkos::resize(position, n_allocated_elements);
621 Kokkos::resize(model,
623 Model::n_var); // use 2nd dim resize if dynamic
624 Kokkos::resize(contribs,
626 Model::n_c); // use 2nd dim resize if dynamic
627 Kokkos::resize(status, n_allocated_elements);
628 Kokkos::resize(ages, n_allocated_elements);
629
630 // Handle resizing for weights based on model type
631 if constexpr (ConstWeightModelType<Model>)
632 {
633 Kokkos::resize(weights,
634 1); // Fixed size for ConstWeightModelType
635 }
636 else
637 {
638 Kokkos::resize(weights, n_allocated_elements);
639 }
640 }
641 }
642 }
643
644 // template <ModelType Model>
645 // void
646 // ParticlesContainer<Model>::__allocate_buffer__()
647 // {
648 // PROFILE_SECTION("ParticlesContainer::__allocate_buffer__")
649
650 // std::size_t buffer_size = buffer_position.extent(0);
651
652 // const double tmp = rt_params.buffer_ratio * n_allocated_elements;
653
654 // const std::size_t buffer_threshold = static_cast<std::size_t>(tmp);
655
656 // if (buffer_size < buffer_threshold)
657 // {
658 // buffer_size = static_cast<std::size_t>(
659 // std::ceil(rt_params.buffer_ratio * n_allocated_elements));
660
661 // // Realloc because not needed to keep buffer as it has been copied
662 // Kokkos::realloc(buffer_position, buffer_size);
663 // Kokkos::realloc(buffer_model, buffer_size, Model::n_var);
664 // buffer_index() = 0;
665 // }
666 // }
667
668 template <ModelType Model>
669 void
671 {
672 PROFILE_SECTION("ParticlesContainer::__allocate_buffer__")
673
674 const auto required_buffer_size = static_cast<std::size_t>(
675 std::ceil(rt_params.buffer_ratio * n_allocated_elements));
676
677 if (buffer_position.extent(0) < required_buffer_size)
678 {
679 // Realloc because not needed to keep buffer as it has been copied
680 Kokkos::realloc(buffer_position, required_buffer_size);
681 Kokkos::realloc(buffer_model, required_buffer_size, Model::n_var);
682 buffer_index() = 0;
683 }
684 }
685
686// NOLINTBEGIN
687#define alloc_without_init(name) \
688 Kokkos::view_alloc(Kokkos::WithoutInitializing, name)
689 // NOLINTEND
690
691 template <ModelType M>
693 std::size_t n_particle)
694 : model(alloc_without_init("particle_model"), 0, 0),
695
696 contribs(alloc_without_init("particle_contribs"), 0),
697 position(alloc_without_init("particle_position"), 0),
698 status(alloc_without_init("particle_status"), 0),
699 weights(alloc_without_init("particle_weigth"), 0),
700 ages(alloc_without_init("particle_age"), 0),
701 buffer_model("buffer_particle_model", 0),
702 buffer_position("buffer_particle_position", 0),
703 buffer_index("buffer_index"), n_allocated_elements(0),
704 n_used_elements(n_particle), inactive_counter(0), rt_params(rt_param)
705 {
706
707 // load_tuning_constant();
708
709 if (n_particle != 0)
710 {
711 _resize(n_particle);
713 }
714
715 // const auto bounds = M::get_bounds();
716
717 // if (begin > end)
718 // {
719 // throw std::invalid_argument("Model begin should be > end");
720 // }
721
722 // begin = bounds.begin;
723 // end = bounds.end;
724 }
725
726 template <ModelType M>
731
732 template <ModelType M>
733 void
735 {
736
737 PROFILE_SECTION("ParticlesContainer::remove_inactive_particles")
738 if (to_remove == 0)
739 {
740 return;
741 }
742
743 if (to_remove == n_used_elements)
744 {
745 _resize(0, true);
746 n_used_elements = 0;
748 }
749 else if (to_remove > n_used_elements)
750 {
751 throw std::runtime_error(
752 "remove_inactive_particles: Error in kernel cannot remove more "
753 "element than existing");
754 }
755 else
756 {
757
758 const auto new_used_item = n_used_elements - to_remove;
759
760 const auto last_used_index = n_used_elements - 1;
761 Kokkos::parallel_scan(
762 "find_and_fill_gap",
763 Kokkos::RangePolicy<ComputeSpace>(0, n_used_elements),
764 CompactParticlesFunctor<M>(status,
765 model,
766 contribs,
767 position,
768 ages,
769 to_remove,
770 last_used_index));
771
772 Kokkos::fence();
773 KOKKOS_ASSERT(this->position.extent(0) == n_allocated_elements);
774 KOKKOS_ASSERT(this->contribs.extent(0) == n_allocated_elements);
775 KOKKOS_ASSERT(this->model.extent(0) == n_allocated_elements);
776 KOKKOS_ASSERT(this->status.extent(0) == n_allocated_elements);
777
778 n_used_elements = new_used_item;
779 const bool do_shrink = n_used_elements <= static_cast<std::size_t>(
780 rt_params.shrink_ratio * n_allocated_elements);
781
782 if (do_shrink)
783 {
784 _resize(n_used_elements * rt_params.allocation_factor, true);
785 }
786 if (do_shrink)
787 {
788 // force to true if we want to shrink
789 _resize(n_used_elements * rt_params.allocation_factor, true);
790 }
792 };
793 }
794
795 template <ModelType M>
796 [[nodiscard]] KOKKOS_INLINE_FUNCTION M::FloatType
797 ParticlesContainer<M>::get_weight(const std::size_t idx) const
798 {
799 if constexpr (ConstWeightModelType<M>)
800 {
801 return weights(0);
802 }
803 else
804 {
805 return weights(idx);
806 }
807 }
808
809 template <ModelType M>
810 void
812 {
813 this->rt_params = parameters;
814 }
815
816 template <ModelType M>
817 void
819 {
820 (void)n_c;
821
822 // PROFILE_SECTION("SORT")
823 // // const auto N = n_used_elements;
824 const int bin_size = 2048;
825
826 const auto sn
827 = Kokkos::subview(position, std::pair<int, int>(0, n_used_elements));
828
829 using ExecSpace = Kokkos::DefaultExecutionSpace;
830 using view_type = decltype(position);
831 auto binop = Kokkos::BinOp1D<view_type>(bin_size, 0, n_c);
832
833 auto sorter = Kokkos::BinSort<view_type, decltype(binop)>(
834 ExecSpace(), sn, binop, true);
835 }
836
837} // namespace MC
838
839#endif
RuntimeParameters rt_params
Definition particles_container.hpp:230
void update_and_remove_inactive(std::size_t out, std::size_t dead)
Definition particles_container.hpp:540
ParticlesContainer(RuntimeParameters rt_param, std::size_t n_particle)
Alias for the model used by the container.
Definition particles_container.hpp:692
double get_allocation_factor() const noexcept
Get the allocation factor.
Definition particles_container.hpp:478
Model::SelfContribs contribs
Definition particles_container.hpp:82
~ParticlesContainer()=default
Default destructor.
void force_remove_dead()
Clean all the non-idle particle even if number smaller than threshold.
Definition particles_container.hpp:462
Model::SelfParticle model
Definition particles_container.hpp:81
KOKKOS_INLINE_FUNCTION std::size_t n_particles() const
Definition particles_container.hpp:448
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:560
Kokkos::View< uint64_t, Kokkos::SharedSpace > buffer_index
Definition particles_container.hpp:223
void _resize(std::size_t new_size, bool force=false)
Definition particles_container.hpp:602
void remove_inactive_particles(std::size_t to_remove)
Definition particles_container.hpp:734
void __allocate_buffer__()
Definition particles_container.hpp:670
ParticlesContainer()
Definition particles_container.hpp:727
ParticlesContainer & operator=(ParticlesContainer &&)=default
std::size_t get_inactive() const noexcept
Definition particles_container.hpp:455
std::size_t capacity() const noexcept
Definition particles_container.hpp:485
uint64_t n_used_elements
Definition particles_container.hpp:225
auto get_buffer_index() const
Definition particles_container.hpp:470
KOKKOS_INLINE_FUNCTION Model::FloatType get_weight(std::size_t idx) const
Return the particle weight.
Definition particles_container.hpp:797
void merge_buffer()
Insert particle buffer into the main container.
Definition particles_container.hpp:576
Model::SelfParticle buffer_model
Definition particles_container.hpp:221
ParticlePositions buffer_position
Definition particles_container.hpp:222
MC::ParticlePositions position
Definition particles_container.hpp:84
void save(Archive &ar) const
Save data into ar for serialization.
Definition particles_container.hpp:526
std::size_t inactive_counter
Definition particles_container.hpp:226
ParticleAges ages
Definition particles_container.hpp:87
Model UsedModel
Definition particles_container.hpp:59
ParticleWeigths< typename Model::FloatType > weights
Definition particles_container.hpp:86
ParticlesContainer & operator=(const ParticlesContainer &)=default
void _sort(size_t n_c)
Definition particles_container.hpp:818
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:493
ParticlesContainer(ParticlesContainer &&)=default
std::size_t n_allocated_elements
Definition particles_container.hpp:224
void change_runtime(RuntimeParameters &&parameters) noexcept
Definition particles_container.hpp:811
MC::ParticleStatus status
Definition particles_container.hpp:85
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
Definition particles_container.hpp:26
void serialize(Archive &ar)
Definition particles_container.hpp:35
double buffer_ratio
Definition particles_container.hpp:28
double dead_particle_ratio_threshold
Definition particles_container.hpp:31
uint64_t minimum_dead_particle_removal
Definition particles_container.hpp:27
double shrink_ratio
Definition particles_container.hpp:30
double allocation_factor
Definition particles_container.hpp:29