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