Utopia 2
Framework for studying models of complex & adaptive systems.
Loading...
Searching...
No Matches
select.hh
Go to the documentation of this file.
1#ifndef UTOPIA_CORE_SELECT_HH
2#define UTOPIA_CORE_SELECT_HH
3
4#include <map>
5#include <set>
6#include <unordered_set>
7#include <random>
8#include <algorithm>
9#include <type_traits>
10
11#include <armadillo>
12#include <yaml-cpp/yaml.h>
13#include <spdlog/spdlog.h> // for fmt::
14
15#include "types.hh"
16#include "exceptions.hh"
17#include "entity.hh"
18#include "cell.hh"
19#include "agent.hh"
20#include "logging.hh"
21#include "../data_io/cfg_utils.hh"
22
63namespace Utopia {
64
65// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
66
67
74
78enum class SelectionMode {
79 // .. Working on entities . . . . . . . . . . . . . . . . . . . . . . . . .
81 condition = 0,
82
84 sample = 1,
85
87 probability = 2,
88
89 // .. Clustering . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
90 // (Offset by 20 to accomodate different algorithms)
92
100
101 // .. Only relevant for CellManager . . . . . . . . . . . . . . . . . . . .
102 // (Offset by 100)
104 position = 100,
105
107 boundary = 101,
108
110 lanes = 102
111
112 // .. Only relevant for AgentManager . . . . . . . . . . . . . . . . . . .
113 // (Offset by 200)
114
115 // .. Only relevant for GraphManager . . . . . . . . . . . . . . . . . . .
116 // (Offset by 300)
117
118 // NOTE When adding new enum members, take care to update the
119 // select_key_map!
120};
121
122
124const std::map<std::string, SelectionMode> selection_mode_map {
125 // General
126 {"condition", SelectionMode::condition},
127 {"sample", SelectionMode::sample},
128 {"probability", SelectionMode::probability},
129
130 // CellManager
131 {"position", SelectionMode::position},
132 {"boundary", SelectionMode::boundary},
133 {"lanes", SelectionMode::lanes},
134
135 // Clustered
136 {"clustered_simple", SelectionMode::clustered_simple}
137};
138
140
143std::string selection_mode_to_string(const SelectionMode& mode) {
144 for (const auto& m : selection_mode_map) {
145 if (m.second == mode) {
146 return m.first;
147 }
148 }
149 // Entry is missing; this should not happen, as the map is meant to
150 // include all possible enum values. Inform about it ...
151 throw std::invalid_argument("The given entity selection mode is not "
152 "available! Are all SelectionMode values represented in the map?");
153};
154
155// end group SelectionModes
161
164template<class M>
166 static constexpr bool value =
167 std::is_same_v<Cell<typename M::Entity::Traits>,
168 typename M::Entity>;
169};
170
172
175template<class M>
177 static constexpr bool value =
178 std::is_same_v<Agent<typename M::Entity::Traits,
179 typename M::Space>,
180 typename M::Entity>;
181};
182
183// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
184// ++ Convenience Wrappers ++++++++++++++++++++++++++++++++++++++++++++++++++++
185
186
188
210template<
211 class Manager,
214{
215 // Determine the selection mode
216 if (not sel_cfg["mode"]) {
217 throw KeyError("mode", sel_cfg, "Could not select entities!");
218 }
219 const auto mode_str = get_as<std::string>("mode", sel_cfg);
220
221 if (not selection_mode_map.count(mode_str)) {
222 throw std::invalid_argument("The given selection mode string ('"
223 + mode_str +"') is invalid! For available modes, consult the "
224 "EntitySelection group in the doxygen documentation.");
225 }
227
228 mngr.log()->debug("Selecting entities using mode '{}' ...", mode_str);
229 mngr.log()->debug("Parameters:\n{}", DataIO::to_string(sel_cfg));
230
231 // Depending on the mode, extract the required parameters and invoke
232 // the mode-specific methods directly
233 // .. Generally available .................................................
234 if (mode == SelectionMode::sample) {
235 const auto N = get_as<int>("num_cells", sel_cfg); // FIXME not general!
237 }
238
239 if (mode == SelectionMode::probability) {
240 const auto p = get_as<double>("probability", sel_cfg);
242 }
243
244 // .. Only for CellManager ................................................
245 if constexpr (is_cell_manager<Manager>::value) {
246 if (mode == SelectionMode::position) {
247 // Populate a vector with SpaceVec objects
248 std::vector<typename Manager::SpaceVec> positions{};
249
250 if (not sel_cfg["positions"]) {
251 throw KeyError("positions", sel_cfg,
252 "Could not select cells by positions!");
253 }
254
255 for (const auto& pos_node : sel_cfg["positions"]) {
256 using SpaceVec = typename Manager::SpaceVec;
257 using ET = typename Manager::SpaceVec::elem_type;
258
259 const auto vec = pos_node.as<std::array<ET, Manager::dim>>();
260
261 // Construct the SpaceVec from the array
262 // NOTE Can be outsourced by making get_as directly accept
263 // nodes instead of (key, parent node) pairs.
264 SpaceVec pos;
265 for (DimType i = 0; i < Manager::dim; i++) {
266 pos[i] = vec[i];
267 }
268 positions.push_back(pos);
269 }
270
272 }
273
274 if (mode == SelectionMode::boundary) {
275 const auto b = get_as<std::string>("boundary", sel_cfg);
277 }
278
279 if (mode == SelectionMode::lanes) {
280 const auto num_v = get_as<unsigned>("num_vertical", sel_cfg);
281 const auto num_h = get_as<unsigned>("num_horizontal", sel_cfg);
282
283 // Handle optional arguments
284 // Permeability
285 auto perm = std::pair<double, double>{0., 0.};
286 auto perm_cfg = sel_cfg["permeability"];
287 if (perm_cfg and perm_cfg.IsSequence()) {
288 perm = perm_cfg.as<std::pair<double, double>>();
289 }
290 else if (perm_cfg and perm_cfg.IsMap()) {
291 perm.first = get_as<double>("horizontal", perm_cfg);
292 perm.second = get_as<double>("vertical", perm_cfg);
293 }
294 else if (perm_cfg and perm_cfg.IsScalar()) {
295 perm.first = perm_cfg.as<double>();
296 perm.second = perm_cfg.as<double>();
297 }
298
299 // Gate width
300 auto gate_width = std::pair<unsigned, unsigned>{0, 0};
301 auto gate_cfg = sel_cfg["gate_width"];
302 if (gate_cfg and gate_cfg.IsSequence()) {
303 gate_width = gate_cfg.as<std::pair<unsigned, unsigned>>();
304 }
305 else if (gate_cfg and gate_cfg.IsMap()) {
306 gate_width.first = get_as<unsigned>("horizontal", gate_cfg);
307 gate_width.second = get_as<unsigned>("vertical", gate_cfg);
308 }
309 else if (gate_cfg and gate_cfg.IsScalar()) {
310 gate_width.first = gate_cfg.as<unsigned>();
311 gate_width.second = gate_cfg.as<unsigned>();
312 }
313
316 }
317
319 const auto p_seed = get_as<double>("p_seed", sel_cfg);
320 const auto p_attach = get_as<double>("p_attach", sel_cfg);
321 const auto num_passes = get_as<unsigned>("num_passes", sel_cfg);
322
323 // TODO Also make the neighborhood mode configurable here
324
325 return
327 p_seed,
328 p_attach,
329 num_passes);
330 }
331 }
332
333 // .. Only for AgentManager ...............................................
334 if constexpr (is_agent_manager<Manager>::value) {
335 // ...
336 }
337
338 // If this point is reached, we did not return, i.e.: no type was available
339 throw std::invalid_argument("The selection mode '"
340 + selection_mode_to_string(mode) + "' is not available for the given "
341 "manager type or via the configuration!");
342}
343
344
345// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
346// ++ General selection functions +++++++++++++++++++++++++++++++++++++++++++++
347
349
354template<
355 SelectionMode mode,
356 class Manager,
358 class Condition = std::function<bool(const std::shared_ptr<typename Manager::Entity>&)>,
359 typename std::enable_if_t<mode == SelectionMode::condition, int> = 0
360 >
362 const Condition& condition)
363{
365 std::copy_if(mngr.entities().begin(), mngr.entities().end(),
366 std::back_inserter(selected),
367 condition);
368 return selected;
369}
370
372
381template<
382 SelectionMode mode,
383 class Manager,
385 typename std::enable_if_t<mode == SelectionMode::sample, int> = 0
386 >
388 const int num_entities)
389{
390 if ( num_entities < 0
391 or static_cast<std::size_t>(num_entities) > mngr.entities().size())
392 {
393 throw std::invalid_argument("Argument num_entities need be in the "
394 "interval [0, entity container size]!");
395 }
396
398 selected.reserve(num_entities);
399
400 // Populate it with the sampled cells, then return
401 std::sample(mngr.entities().begin(), mngr.entities().end(),
402 std::back_inserter(selected),
403 static_cast<std::size_t>(num_entities), *mngr.rng());
404 return selected;
405}
406
408
417template<
418 SelectionMode mode,
419 class Manager,
421 typename std::enable_if_t<mode == SelectionMode::probability, int> = 0
422 >
424 const double probability)
425{
426 // Before drawing a huge amount of random numbers, check obvious cases
427 if (probability == 0.) {
428 return {};
429 }
430 else if (probability == 1.) {
431 return mngr.entities();
432 }
434 throw std::invalid_argument("Entity selection in mode 'probability' "
435 "failed due to probability argument outside of interval [0., 1.]");
436 }
437
438 // Build the distribution, selection condition lambda, and select ...
439 std::uniform_real_distribution<> dist(0., 1.);
440
441 return
443 mngr,
444 // Declare a mutable lambda (needed for dist)
445 [&, dist{std::move(dist)}](const auto&) mutable {
446 return (dist(*mngr.rng()) < probability);
447 }
448 );
449}
450
451// ++ Cell-based selection functions ++++++++++++++++++++++++++++++++++++++++++
452
454
463template<
464 SelectionMode mode,
465 class Manager,
467 typename std::enable_if_t<mode == SelectionMode::position, int> = 0
468 >
470 const std::vector<typename Manager::SpaceVec>& positions)
471{
473 for (const auto& pos : positions) {
474 selected.push_back(mngr.cell_at(pos));
475 }
476 return selected;
477}
478
480
486template<
487 SelectionMode mode,
488 class Manager,
490 typename std::enable_if_t<mode == SelectionMode::boundary, int> = 0
491 >
493 const std::string& boundary)
494{
495 return mngr.boundary_cells(boundary);
496}
497
498
500
534template<
535 SelectionMode mode,
536 class Manager,
538 typename std::enable_if_t<mode == SelectionMode::lanes, int> = 0
539 >
541 const Manager& mngr,
542 const unsigned int num_vertical,
543 const unsigned int num_horizontal,
544 const std::pair<double, double> permeability = {0., 0.},
545 const std::pair<unsigned int, unsigned int> gate_width = {0, 0}
546)
547{
548 static_assert(Manager::Space::dim == 2, "Only 2D space is supported.");
549 using SpaceVec = typename Manager::SpaceVec;
550 using MultiIndex = typename Manager::MultiIndex;
551
552 // Get the shared pointers to the grid and some further information
553 const auto grid = mngr.grid();
554 const MultiIndex shape = grid->shape();
555 const auto num_cells = grid->num_cells();
556 const SpaceVec extent = grid->space()->extent;
557 const auto eff_resolution = grid->effective_resolution();
558
559 // The number of lanes should not exceed the number of cells
560 if (num_vertical >= shape[0] or num_horizontal >= shape[1]) {
561 throw std::invalid_argument("Given number of vertical and/or "
562 "horizontal lanes is equal or larger to the number of cells along "
563 "that dimension! Choose a smaller value.");
564 }
565
566 // Check permeability
567 if (permeability.first < 0. or permeability.first > 1.) {
568 throw std::invalid_argument(fmt::format(
569 "Permeability in horizontal lanes needs to be in interval "
570 "[0., 1.], but was: {}", permeability.first)
571 );
572 }
573 if (permeability.second < 0. or permeability.second > 1.) {
574 throw std::invalid_argument(fmt::format(
575 "Permeability in vertical lanes needs to be in interval "
576 "[0., 1.], but was: {}", permeability.second)
577 );
578 }
579
580 // Emit information
581 mngr.log()->debug(
582 "Selecting cells for lanes ...\n"
583 " num: {} horizontal, \t{} vertical\n"
584 " permeability: {} horizontal, \t{} vertical\n"
585 " gate width: {} horizontal, \t{} vertical\n",
587 permeability.first, permeability.second,
588 gate_width.first, gate_width.second
589 );
590
591 // .. Lanes ...............................................................
592 // Define the required variables for vertical and horizontal lanes. It is
593 // important to work on absolute positions such that rounding errors are
594 // not propagated along the grid ...
595 // Do all this vector based to be able to use armadillo
596 const auto num_lanes = MultiIndex{num_vertical, num_horizontal};
597 SpaceVec lane_start, lane_step;
598
599 if (grid->is_periodic()) {
600 lane_start.fill(0.);
601 lane_step = extent / num_lanes;
602 }
603 else {
604 lane_start = extent / (num_lanes + 1);
606 }
607
608 // Determine x- and y-indices for all the lanes that can be reached with
609 // these positions. To avoid rounding errors, use the absolute position to
610 // find the first cells of each lane: construct a proxy position and then
611 // ask the grid what the corresponding multi index is. The respective
612 // component can then be used to select the lanes.
613 // Using sets to have faster lookups
614 std::set<IndexType> indices_x, indices_y;
615
616 for (unsigned int i = 0; i < num_vertical; i++) {
617 const SpaceVec proxy_pos = {lane_start[0] + i * lane_step[0], 0.};
618 indices_x.insert(grid->midx_of(grid->cell_at(proxy_pos))[0]);
619 }
620
621 for (unsigned int i = 0; i < num_horizontal; i++) {
622 const SpaceVec proxy_pos = {0., lane_start[1] + i * lane_step[1]};
623 indices_y.insert(grid->midx_of(grid->cell_at(proxy_pos))[1]);
624 }
625
626 // .. Gates in lanes ......................................................
627 // Gates are centered between the lanes
628 auto num_gates = num_lanes;
629 SpaceVec gate_start, gate_step;
630 const SpaceVec grid_step = 1./eff_resolution;
631
632 if (grid->is_periodic()) {
633 gate_step = extent / num_gates;
634 }
635 else {
636 num_gates = num_gates + 1;
637 gate_step = extent / num_gates;
638 }
639
640 // center of gate at gate_step / 2.
641 // but, want lower edge of the gate and iterate from there
642 // hence, distinguish pair and impair gates width
643 if (gate_width.first % 2 == 0) {
644 gate_start = gate_step / 2. - SpaceVec(
645 {grid_step[0] * (gate_width.first - 1) / 2.,
646 grid_step[1] * (gate_width.second - 1) / 2.});
647 }
648 else {
649 gate_start = gate_step / 2. - SpaceVec(
650 {grid_step[0] * (gate_width.first) / 2.,
651 grid_step[1] * (gate_width.second) / 2.});
652 }
653
654 // Determine x- and y-indices for every gate
655 // Need error handling here because with a large gate width in non-periodic
656 // space, the grid->cell_at lookup might throw std::invalid_argument
657 std::set<IndexType> gates_indices_x, gates_indices_y;
658 SpaceVec proxy_pos;
659
660 try {
661 for (unsigned int i = 0; i < num_gates[0]; i++) {
662 for (unsigned int j = 0; j < gate_width.first; j++) {
663 proxy_pos = {gate_start[0] + i*gate_step[0] + j*grid_step[0],
664 0.};
665 gates_indices_x.insert(
666 grid->midx_of(grid->cell_at(proxy_pos))[0]
667 );
668 }
669 }
670 for (unsigned int i = 0; i < num_gates[1]; i++) {
671 for (unsigned int j = 0; j < gate_width.second; j++) {
672 proxy_pos = {0.,
673 gate_start[1] + i*gate_step[1] + j*grid_step[1]};
674 gates_indices_y.insert(
675 grid->midx_of(grid->cell_at(proxy_pos))[1]
676 );
677 }
678 }
679 }
680 catch (std::invalid_argument& exc) {
681 throw std::invalid_argument(fmt::format(
682 "Failed to determine gate cells for lane selection, presumably "
683 "because the gate width was chosen larger than the compartment "
684 "size. Check that the gate width (h: {:d}, v: {:d}) fits into the "
685 "compartment. Grid shape: ({:d} x {:d}, {}). "
686 "Number of lanes: (h: {:d}, v: {:d}).",
687 gate_width.first, gate_width.second,
688 shape[0], shape[1],
689 grid->is_periodic() ? "periodic" : "non-periodic",
691 ));
692 }
693
694 // .. ID selection ........................................................
695 // Now need a container for selected cell IDs
696 std::vector<IndexType> selected_ids{};
697
698 // Build the distribution, selection condition lambda, and select ...
699 std::uniform_real_distribution<> dist(0., 1.);
700
701 // Populate it by iterating over all grid cell IDs, determining their
702 // multi index, and then checking it against the containers of cells.
703 // NOTE There is hardly a way around std::set::find if one wants to
704 // ascertain that the lanes are distributed evenly on the grid, which
705 // requires to determine the desired multi index components explicitly
706 // rather than calculating them via modulo operations (which adds
707 // rounding errors that are propagated over the grid). Still, the
708 // std::set::find method is rather efficient (logarithmic complexity)
709 // as it operates on a tree and the indices can be easily sorted.
710 for (IndexType cell_id = 0; cell_id < num_cells; cell_id++) {
711 const auto midx = grid->midx_of(cell_id);
712
713 // Check whether this cell belongs to a horizontal lane and
714 // if so, add its index to the container of indices.
715 if (indices_y.find(midx[1]) != indices_y.end()) {
716 // skip because this cell is a gate
717 if (gates_indices_x.find(midx[0]) != gates_indices_x.end()) {
718 continue;
719 }
720 // skip because of permeability
721 else if (permeability.first > 0. and
722 dist(*mngr.rng()) < permeability.first)
723 {
724 continue;
725 }
726
727 // else: not skipped. Add the cell ID
728 selected_ids.push_back(cell_id);
729 }
730 // Do the same for cells belonging to vertical lanes
731 else if (indices_x.find(midx[0]) != indices_x.end()) {
732 // skip because this cell is a gate
733 if (gates_indices_y.find(midx[1]) != gates_indices_y.end()) {
734 continue;
735 }
736 // skip because of permeability
737 else if (permeability.second > 0. and
738 dist(*mngr.rng()) < permeability.second)
739 {
740 continue;
741 }
742
743 // else: not skipped. Add the cell ID
744 selected_ids.push_back(cell_id);
745 }
746 }
747 // Alternative implementation: For each of the entries in the indices_*
748 // containers, construct a full multi index and then compute the cell ID
749 // from it. However, the Grid does not currently supply a method to
750 // calculate the ID from a given multi index.
751
752 mngr.log()->debug("Selected {:d} / {:d} cells using mode 'lanes'.",
753 selected_ids.size(), mngr.cells().size());
754
755 // Return as container of shared pointers to cell objects
756 return mngr.entity_pointers_from_ids(selected_ids);
757}
758
759
761
774template<
775 SelectionMode mode,
776 class Manager,
778 typename std::enable_if_t<mode == SelectionMode::clustered_simple, int> = 0
779 >
781 const double p_seed,
782 const double p_attach,
783 const unsigned int num_passes)
784{
786 throw std::invalid_argument("Argument p_attach needs to be a "
787 "probability, i.e. be in interval [0., 1.]!");
788 }
789
790 mngr.log()->debug("Selecting cell clusters ... (p_seed: {}, p_attach: {}, "
791 "num_passes: {})", p_seed, p_attach, num_passes);
792
793 // Get an initial selection of clustering "seeds"
795 p_seed);
796
797 mngr.log()->debug("Selected {} clustering seeds.", seeds.size());
798
799 // Need to work on a set and also need a temporary container for those
800 // cells that are to be added after each pass.
801 // To make this more efficient, work on cell IDs instead of shared_ptr.
802 std::unordered_set<IndexType> selected_ids{};
803 std::unordered_set<IndexType> ids_to_attach{};
804
805 // Populate the set of selected IDs
806 for (const auto& cell : seeds) {
807 selected_ids.insert(cell->id());
808 }
809
810 // For probabilities, need a distribution object
811 std::uniform_real_distribution<> dist(0., 1.);
812
813 // Do multiple passes ...
814 for (unsigned int i = 0; i < num_passes; i++) {
815 // ... in which all already selected cells are iterated over and
816 // each cell's neighbours are added with the given attachment
817 // probability
818
819 // Determine which cell IDs to attach
820 ids_to_attach.clear();
821 for (const auto cell_id : selected_ids) {
822 for (const auto nb_id : mngr.grid()->neighbors_of(cell_id)) {
823 if (dist(*mngr.rng()) < p_attach) {
824 ids_to_attach.insert(nb_id);
825 }
826 }
827 }
828
829 // Add them to the set of selected cells
830 selected_ids.insert(ids_to_attach.begin(), ids_to_attach.end());
831
832 mngr.log()->debug("Finished pass {}. Have {} cells selected now.",
833 i + 1, selected_ids.size());
834 }
835
836 // Convert into container of pointers to cells and return
837 return
838 mngr.entity_pointers_from_ids(
839 std::vector<IndexType>(selected_ids.begin(), selected_ids.end())
840 );
841}
842
843
844// ++ Agent-based selection functions +++++++++++++++++++++++++++++++++++++++++
845// ... nothing yet.
846
847// end group EntitySelection
852} // namespace Utopia
853
854#endif // UTOPIA_CORE_SELECT_HH
An agent is a slightly specialized state container.
Definition agent.hh:47
For access to a dict-like structure with a bad key.
Definition exceptions.hh:67
YAML::Node Config
Type of a variadic dictionary-like data structure used throughout Utopia.
Definition types.hh:71
std::string to_string(const Config &node)
Given a config node, returns a string representation of it.
Definition cfg_utils.hh:110
Container select_entities(const Manager &mngr, const DataIO::Config &sel_cfg)
Select entities according to parameters specified in a configuration.
Definition select.hh:213
std::string selection_mode_to_string(const SelectionMode &mode)
Given a SelectionMode enum value, return the corresponding string key.
Definition select.hh:143
SelectionMode
Possible selection modes; availability depends on choice of manager.
Definition select.hh:78
const std::map< std::string, SelectionMode > selection_mode_map
A map from strings to Select enum values.
Definition select.hh:124
@ lanes
(For CellManager only) Selects horizontal or vertical lanes of cells
@ condition
Select if a condition is fulfilled.
@ position
(For CellManager only) Selects cells at given positions in space
@ sample
Select a random sample of entities with a known sample size.
@ probability
Select an entity with a given probability.
@ boundary
(For CellManager only) Select the boundary cells of a grid
@ clustered_simple
Select entity clusters using a simple neighborhood-based algorithm.
Definition agent.hh:11
std::vector< std::shared_ptr< EntityType > > EntityContainer
Type of the variably sized container for entities.
Definition types.hh:22
unsigned short DimType
Type for dimensions, i.e. very small unsigned integers.
Definition types.hh:34
std::size_t IndexType
Type for indices, i.e. values used for container indexing, agent IDs, ...
Definition types.hh:40
Metafunction to determine whether a Manager is an AgentManager.
Definition select.hh:176
static constexpr bool value
Definition select.hh:177
Metafunction to determine whether a Manager is a CellManager.
Definition select.hh:165
static constexpr bool value
Definition select.hh:166