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
64
66 std::size_t n_particle,
67 std::size_t _n_samples);
68 ParticlesContainer(); //=default;
76
81
82 // NOLINTBEGIN(cppcoreguidelines-non-private-member-variables-in-classes)
83 Model::SelfParticle model;
89 // NOLINTEND(cppcoreguidelines-non-private-member-variables-in-classes)
90
92 KOKKOS_INLINE_FUNCTION auto sample(std::size_t idx,
93 std::size_t i_sample) const;
94
96 KOKKOS_INLINE_FUNCTION auto samples();
97
102 template <typename CviewType>
103 KOKKOS_INLINE_FUNCTION void
104 get_contributions(const std::size_t idx,
105 const CviewType& contributions) const
106 {
107 if (begin < end)
108 {
109 static_assert(ConstWeightModelType<Model>,
110 "ModelType: Constapply_weight()");
111
112 const double weight = get_weight(idx);
113 const auto pos = position(idx);
114 auto access = contributions.access();
115
116 for (int i = begin; i < end; ++i)
117 {
118 const int rel = i - begin;
119 access(rel, pos) += weight * model(idx, i);
120 }
121 }
122 }
123
137 [[nodiscard]] KOKKOS_INLINE_FUNCTION bool
138 handle_division(const MC::pool_type& random_pool, std::size_t idx1) const;
139
143 [[nodiscard]] KOKKOS_INLINE_FUNCTION double
144 get_weight(std::size_t idx) const;
145
146 // HOST
147
153
158
162 template <class Archive> void save(Archive& ar) const;
163
167 template <class Archive> void load(Archive& ar);
168
172 [[nodiscard]] double get_allocation_factor() const noexcept;
173
174 void change_nsample(std::size_t new_n_sample);
179 [[nodiscard]] std::size_t capacity() const noexcept;
180
184 [[nodiscard]] std::size_t get_inactive() const noexcept;
185
191 void update_and_remove_inactive(std::size_t out, std::size_t dead);
192
199 [[nodiscard]] KOKKOS_INLINE_FUNCTION std::size_t n_particles() const;
200
201 void change_runtime(RuntimeParameters&& parameters) noexcept;
202
203 void _sort(size_t n_c);
204
222 void remove_inactive_particles(std::size_t to_remove);
223
224// FIXME: used only in unit test
225#ifndef NDEBUG
226 [[maybe_unused]] [[nodiscard]] auto get_buffer_index() const;
227#endif
228
229 private:
230 Model::SelfParticle buffer_model;
232 Kokkos::View<uint64_t, Kokkos::SharedSpace> buffer_index;
235 std::size_t inactive_counter;
236
238 void _resize(std::size_t new_size, bool force = false);
239 std::size_t n_samples;
241 // FIXME
242 public:
243 int begin;
244 int end;
245
246 // int begin;
247 // int end;
248 };
249
250} // namespace MC
251
252/*
253 * IMPLEMENTATION
254 */
255namespace MC
256{
257 namespace
258 {
259
260 using TeamPolicy = Kokkos::TeamPolicy<ComputeSpace>;
261 using TeamMember = TeamPolicy::member_type;
262
301 template <ModelType M> struct CompactParticlesFunctor
302 {
303
304 CompactParticlesFunctor(MC::ParticleStatus _status,
305 M::SelfParticle _model,
306 MC::ParticlePositions _position,
307 MC::ParticleAges _ages,
308 std::size_t _to_remove,
309 std::size_t _last_used_index)
310 : status(std::move(_status)), model(std::move(_model)),
311 position(std::move(_position)), ages(std::move(_ages)),
312 offset("offset"), to_remove(_to_remove),
313 last_used_index(_last_used_index)
314 {
315
316 Kokkos::deep_copy(offset, 0);
317 }
318
319 KOKKOS_INLINE_FUNCTION void
320 operator()(const int i, std::size_t& update, const bool final) const
321 {
322
329
330 // Check if the particle is non-idle.
331 const bool is_inactive = (status(i) != MC::Status::Idle);
332
333 // Capture the current prefix sum of inactive particles.
334 const std::size_t scan_index = update;
335
336 // Increment the shared counter if the particle is inactive.
337 update += is_inactive ? 1 : 0;
338
339 // Final pass: Compact the container by replacing inactive particles.
340 if (final && is_inactive && scan_index < to_remove)
341 {
342
343 const auto inactive_slot = i; // Index of the "gap" to fill
344
345 // Atomically find a replacement particle from the end of the
346 // container. The replacement must be alive (idle) and not the same as
347 // the particle being removed.
348 // Index of the particle that will "fill" the inactive slot
349 auto replacement_index
350 = last_used_index - Kokkos::atomic_fetch_add(&offset(), 1);
351 while (status(replacement_index) != MC::Status::Idle
352 || replacement_index
353 == static_cast<std::size_t>(inactive_slot))
354 {
355 replacement_index
356 = last_used_index - Kokkos::atomic_fetch_add(&offset(), 1);
357 }
358
359 // Mark the removed particle's position as idle.
360 status(inactive_slot) = MC::Status::Idle;
361
362 // Merge position EZ
363 // No need atomic: final executed exactly once
364 Kokkos::atomic_exchange(&position(inactive_slot),
365 position(replacement_index));
366
367 // TODO Use hierachical parallism here, thread range is likely to work
368 for (std::size_t i_properties = 0; i_properties < M::n_var;
369 ++i_properties)
370 {
371 model(inactive_slot, i_properties)
372 = model(replacement_index, i_properties);
373 }
374 ages(inactive_slot, 0) = ages(replacement_index, 0);
375 ages(inactive_slot, 1) = ages(replacement_index, 1);
376 }
377 }
378
379 MC::ParticleStatus status;
380 M::SelfParticle model;
381 MC::ParticlePositions position;
382 MC::ParticleAges ages;
383 Kokkos::View<std::size_t, ComputeSpace> offset;
384 std::size_t to_remove;
385 std::size_t last_used_index;
386 };
387
404 template <ModelType M> struct InsertFunctor
405 {
406 InsertFunctor(std::size_t _original_size,
407 M::SelfParticle _model,
408 MC::ParticlePositions _position,
409 MC::ParticleAges _ages,
410 M::SelfParticle _buffer_model,
411 MC::ParticlePositions _buffer_position)
412 : original_size(_original_size), model(std::move(_model)),
413 ages(std::move(_ages)), position(std::move(_position)),
414 buffer_model(std::move(_buffer_model)),
415 buffer_position(std::move(_buffer_position))
416 {
417 }
418 KOKKOS_INLINE_FUNCTION
419 void
420 operator()(const TeamMember& team) const
421 {
422 auto range = M::n_var;
423 const int i = team.league_rank();
424
425 Kokkos::parallel_for(
426 Kokkos::TeamVectorRange(team, range),
427 [&](const int& j)
428 { model(original_size + i, j) = buffer_model(i, j); });
429 position(original_size + i) = buffer_position(i);
430
431 // Actually needs buffer to store mother's hydraulic time
432 // But set new hydraulic time to 0 to not create new buffer a save
433 // memory usage
434 ages(original_size + i, 0) = 0;
435 ages(original_size + i, 1) = 0;
436 }
437
438 std::size_t original_size;
439 M::SelfParticle model;
440 MC::ParticleAges ages;
441 MC::ParticlePositions position;
442 M::SelfParticle buffer_model;
443 MC::ParticlePositions buffer_position;
444 };
445
446 }; // namespace
447
448 template <ModelType Model>
449 [[nodiscard]] KOKKOS_INLINE_FUNCTION std::size_t
454
455 template <ModelType Model>
456 [[nodiscard]] std::size_t
458 {
459 return inactive_counter;
460 }
461
462 template <ModelType Model>
463 void
468#ifndef NDEBUG
469
470 template <ModelType Model>
471 [[maybe_unused]] [[nodiscard]] auto
476#endif
477
478 template <ModelType Model>
479 [[nodiscard]] double
481 {
482 return rt_params.allocation_factor;
483 }
484
485 template <ModelType Model>
486 [[nodiscard]] std::size_t
488 {
490 }
491
492 template <ModelType Model>
493 KOKKOS_INLINE_FUNCTION auto
498 template <ModelType Model>
499 KOKKOS_INLINE_FUNCTION auto
501 const std::size_t i_sample) const
502 {
503 return random(idx, i_sample);
504 }
505
506 template <ModelType Model>
507 template <class Archive>
508 void
510 {
511 // Basically store everything that is usefull
513
514 // Allocation is done in the deserialize
515 deserialize_view(ar, weights);
516 deserialize_view(ar, position);
517 deserialize_view(ar, status);
518 deserialize_view(ar, model);
519 deserialize_view(ar, ages);
520#ifndef NDEBUG
521 Kokkos::printf("ParticlesContainer::load: Check if load_tuning_constant "
522 "works with different value");
523#endif
524
525 __allocate_buffer__(); // Dont forget to allocate buffer
526 }
527
528 template <ModelType Model>
529 template <class Archive>
530 void
532 {
533
534 // Basically store everything that is usefull
536 serialize_view(ar, weights);
537 serialize_view(ar, position);
538 serialize_view(ar, status);
539 serialize_view(ar, model);
540 serialize_view(ar, ages);
541 }
542
543 template <ModelType Model>
544 void
545 ParticlesContainer<Model>::change_nsample(const std::size_t new_n_sample)
546 {
547 if (new_n_sample != n_samples)
548 {
549 n_samples = new_n_sample;
550 Kokkos::resize(random, n_allocated_elements, n_samples);
551 }
552 }
553
554 template <ModelType Model>
555 void
557 const std::size_t dead)
558 {
559 // Actually out particle and dead ones are just inactive
560 inactive_counter += out;
561 inactive_counter += dead;
562
563 const auto _threshold = std::max(
564 rt_params.minimum_dead_particle_removal,
565 static_cast<uint64_t>(static_cast<double>(n_used_elements)
566 * rt_params.dead_particle_ratio_threshold));
567
568 if (inactive_counter > _threshold)
569 {
571 }
572 }
573
574 template <ModelType Model>
575 KOKKOS_INLINE_FUNCTION bool
577 std::size_t idx1) const
578 {
579 if (Kokkos::atomic_load(&buffer_index()) < buffer_model.extent(0))
580 {
581 const auto idx2 = Kokkos::atomic_fetch_add(&buffer_index(), 1);
582 Model::division(random_pool, idx1, idx2, model, buffer_model);
583 buffer_position(idx2) = position(idx1);
584 ages(idx1, 1) = 0;
585 return true;
586 }
587 return false;
588 }
589
590 template <ModelType Model>
591 void
593 {
594 PROFILE_SECTION("ParticlesContainer::merge_buffer")
595 const auto original_size = n_used_elements;
596 const auto n_add_item = buffer_index();
597 if (n_add_item == 0)
598 {
599 return;
600 }
601 _resize(original_size + n_add_item);
602 Kokkos::parallel_for("insert_merge",
603 TeamPolicy(n_add_item, Kokkos::AUTO, Model::n_var),
604 InsertFunctor<Model>(original_size,
605 model,
606 position,
607 ages,
610
611 buffer_index() = 0;
612 n_used_elements += n_add_item;
614 }
615
616 template <ModelType Model>
617 void
618 ParticlesContainer<Model>::_resize(std::size_t new_size, bool force)
619 {
620 PROFILE_SECTION("ParticlesContainer::_resize")
621
622 // Ensure new_size is greater than zero
623 if (new_size > 0)
624 {
625 // Determine if resizing is necessary based on the condition
626 if (new_size > n_allocated_elements || force)
627 {
628 // Calculate the new allocated size
629 const auto new_allocated_size = static_cast<std::size_t>(std::ceil(
630 static_cast<double>(new_size) * rt_params.allocation_factor));
631
632 // Update the allocated size
633 n_allocated_elements = new_allocated_size;
634
635 // Perform the resizing on all relevant data containers
636 Kokkos::resize(position, n_allocated_elements);
637 Kokkos::resize(model,
639 Model::n_var); // use 2nd dim resize if dynamic
640 Kokkos::resize(status, n_allocated_elements);
641 Kokkos::resize(ages, n_allocated_elements);
642
643 if (n_samples != 0)
644 {
645 Kokkos::resize(random, n_allocated_elements, n_samples);
646 }
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 Kokkos::realloc(buffer_position, required_buffer_size);
699 Kokkos::realloc(buffer_model, required_buffer_size, Model::n_var);
700 buffer_index() = 0;
701 }
702 }
703
704// NOLINTBEGIN
705#define alloc_without_init(name) \
706 Kokkos::view_alloc(Kokkos::WithoutInitializing, name)
707 // NOLINTEND
708
709 template <ModelType M>
711 std::size_t n_particle,
712 std::size_t _n_samples)
713 : model(alloc_without_init("particle_model"), 0, 0),
714 position(alloc_without_init("particle_position"), 0),
715 status(alloc_without_init("particle_status"), 0),
716 weights(alloc_without_init("particle_weigth"), 0),
717 ages(alloc_without_init("particle_age"), 0),
718 random(alloc_without_init("particle_random"), 0, 0),
719 buffer_model("buffer_particle_model", 0),
720 buffer_position("buffer_particle_position", 0),
721 buffer_index("buffer_index"), n_allocated_elements(0),
722 n_used_elements(n_particle), inactive_counter(0), n_samples(_n_samples),
723 rt_params(rt_param), begin(0), end(0)
724 {
725
726 // load_tuning_constant();
727
728 if (n_particle != 0)
729 {
730 _resize(n_particle);
732 }
733
734 const auto bounds = M::get_bounds();
735
736 if (begin > end)
737 {
738 throw std::invalid_argument("Model begin should be > end");
739 }
740
741 begin = bounds.begin;
742 end = bounds.end;
743 }
744
745 template <ModelType M>
750
751 template <ModelType M>
752 void
754 {
755
756 PROFILE_SECTION("ParticlesContainer::remove_inactive_particles")
757 if (to_remove == 0)
758 {
759 return;
760 }
761
762 if (to_remove == n_used_elements)
763 {
764 _resize(0, true);
765 n_used_elements = 0;
767 }
768 else if (to_remove > n_used_elements)
769 {
770 throw std::runtime_error(
771 "remove_inactive_particles: Error in kernel cannot remove more "
772 "element than existing");
773 }
774 else
775 {
776
777 const auto new_used_item = n_used_elements - to_remove;
778
779 const auto last_used_index = n_used_elements - 1;
780 Kokkos::parallel_scan(
781 "find_and_fill_gap",
782 Kokkos::RangePolicy<ComputeSpace>(0, n_used_elements),
783 CompactParticlesFunctor<M>(
784 status, model, position, ages, to_remove, last_used_index));
785
786 Kokkos::fence();
787 KOKKOS_ASSERT(this->position.extent(0) == n_allocated_elements);
788 KOKKOS_ASSERT(this->model.extent(0) == n_allocated_elements);
789 KOKKOS_ASSERT(this->status.extent(0) == n_allocated_elements);
790#ifndef NDEBUG
791 if (n_samples != 0)
792 {
793 KOKKOS_ASSERT(this->random.extent(0) == n_allocated_elements);
794 KOKKOS_ASSERT(this->random.extent(1) == n_samples);
795 }
796#endif
797
798 n_used_elements = new_used_item;
799 const bool do_shrink = n_used_elements <= static_cast<std::size_t>(
800 rt_params.shrink_ratio * n_allocated_elements);
801
802 if (do_shrink)
803 {
804 _resize(n_used_elements * rt_params.allocation_factor, true);
805 }
806 if (do_shrink)
807 {
808 // force to true if we want to shrink
809 _resize(n_used_elements * rt_params.allocation_factor, true);
810 }
812 };
813 }
814
815 template <ModelType M>
816 [[nodiscard]] KOKKOS_INLINE_FUNCTION double
817 ParticlesContainer<M>::get_weight(const std::size_t idx) const
818 {
819 if constexpr (ConstWeightModelType<M>)
820 {
821 return weights(0);
822 }
823 else
824 {
825 return weights(idx);
826 }
827 }
828
829 template <ModelType M>
830 void
832 {
833 this->rt_params = parameters;
834 }
835
836 template <ModelType M>
837 void
839 {
840 (void)n_c;
841 }
842
843} // namespace MC
844
845#endif
RuntimeParameters rt_params
Definition particles_container.hpp:240
void update_and_remove_inactive(std::size_t out, std::size_t dead)
Definition particles_container.hpp:556
double get_allocation_factor() const noexcept
Get the allocation factor.
Definition particles_container.hpp:480
~ParticlesContainer()=default
Default destructor.
void force_remove_dead()
Clean all the non-idle particle even if number smaller than threshold.
Definition particles_container.hpp:464
Model::SelfParticle model
Definition particles_container.hpp:83
KOKKOS_INLINE_FUNCTION std::size_t n_particles() const
Definition particles_container.hpp:450
ParticleWeigths weights
Definition particles_container.hpp:86
int end
Definition particles_container.hpp:244
KOKKOS_INLINE_FUNCTION bool handle_division(const MC::pool_type &random_pool, std::size_t idx1) const
Attempts to spawn a new particle by performing division on the specified particle.
Definition particles_container.hpp:576
ParticleSamples random
Definition particles_container.hpp:88
Kokkos::View< uint64_t, Kokkos::SharedSpace > buffer_index
Definition particles_container.hpp:232
void _resize(std::size_t new_size, bool force=false)
Definition particles_container.hpp:618
void change_nsample(std::size_t new_n_sample)
Definition particles_container.hpp:545
void remove_inactive_particles(std::size_t to_remove)
Definition particles_container.hpp:753
void __allocate_buffer__()
Definition particles_container.hpp:688
ParticlesContainer()
Definition particles_container.hpp:746
ParticlesContainer & operator=(ParticlesContainer &&)=default
std::size_t get_inactive() const noexcept
Definition particles_container.hpp:457
std::size_t capacity() const noexcept
Definition particles_container.hpp:487
uint64_t n_used_elements
Definition particles_container.hpp:234
auto get_buffer_index() const
Definition particles_container.hpp:472
KOKKOS_INLINE_FUNCTION void get_contributions(const std::size_t idx, const CviewType &contributions) const
Get the contribution if particle at index idx.
Definition particles_container.hpp:104
void merge_buffer()
Insert particle buffer into the main container.
Definition particles_container.hpp:592
Model::SelfParticle buffer_model
Definition particles_container.hpp:230
KOKKOS_INLINE_FUNCTION double get_weight(std::size_t idx) const
Return the particle weight.
Definition particles_container.hpp:817
std::size_t n_samples
Definition particles_container.hpp:239
KOKKOS_INLINE_FUNCTION auto sample(std::size_t idx, std::size_t i_sample) const
get the sample at idx/i_sample position
Definition particles_container.hpp:500
ParticlesContainer(RuntimeParameters rt_param, std::size_t n_particle, std::size_t _n_samples)
Alias for the model used by the container.
Definition particles_container.hpp:710
ParticlePositions buffer_position
Definition particles_container.hpp:231
MC::ParticlePositions position
Definition particles_container.hpp:84
void save(Archive &ar) const
Save data into ar for serialization.
Definition particles_container.hpp:531
std::size_t inactive_counter
Definition particles_container.hpp:235
ParticleAges ages
Definition particles_container.hpp:87
Model UsedModel
Definition particles_container.hpp:59
ParticlesContainer & operator=(const ParticlesContainer &)=default
void _sort(size_t n_c)
Definition particles_container.hpp:838
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:509
ParticlesContainer(ParticlesContainer &&)=default
std::size_t n_allocated_elements
Definition particles_container.hpp:233
void change_runtime(RuntimeParameters &&parameters) noexcept
Definition particles_container.hpp:831
int begin
Definition particles_container.hpp:243
KOKKOS_INLINE_FUNCTION auto samples()
get the sample view
Definition particles_container.hpp:494
MC::ParticleStatus status
Definition particles_container.hpp:85
Concept to check if a model type has uniform_weight
Definition traits.hpp:183
Namespace that contains classes and structures related to Monte Carlo (MC) simulations.
Definition alias.hpp:15
Kokkos::View< double *, ComputeSpace > ParticleWeigths
Definition alias.hpp:75
Kokkos::View< float **, Kokkos::LayoutRight, ComputeSpace > ParticleSamples
Definition alias.hpp:81
Kokkos::View< Status *, ComputeSpace > ParticleStatus
Definition alias.hpp:74
Kokkos::View< uint64_t *, ComputeSpace > ParticlePositions
Definition alias.hpp:73
@ Idle
Definition alias.hpp:59
ParticleAgesBase< ComputeSpace > ParticleAges
Definition alias.hpp:76
gen_pool_type< Kokkos::DefaultExecutionSpace > pool_type
Definition alias.hpp:39
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