1 #ifndef UTOPIA_CORE_SELECT_HH
2 #define UTOPIA_CORE_SELECT_HH
6 #include <unordered_set>
12 #include <yaml-cpp/yaml.h>
13 #include <spdlog/spdlog.h>
21 #include "../data_io/cfg_utils.hh"
137 if (m.second == mode) {
143 throw std::invalid_argument(
"The given entity selection mode is not "
144 "available! Are all SelectionMode values represented in the map?");
154 std::is_same_v<Cell<typename M::Entity::Traits>,
165 std::is_same_v<
Agent<
typename M::Entity::Traits,
203 if (not sel_cfg[
"mode"]) {
204 throw KeyError(
"mode", sel_cfg,
"Could not select entities!");
206 const auto mode_str = get_as<std::string>(
"mode", sel_cfg);
209 throw std::invalid_argument(
"The given selection mode string ('"
210 + mode_str +
"') is invalid! For available modes, consult the "
211 "EntitySelection group in the doxygen documentation.");
215 mngr.log()->debug(
"Selecting entities using mode '{}' ...", mode_str);
222 const auto N = get_as<int>(
"num_cells", sel_cfg);
223 return select_entities<SelectionMode::sample>(mngr, N);
227 const auto p = get_as<double>(
"probability", sel_cfg);
228 return select_entities<SelectionMode::probability>(mngr, p);
235 std::vector<typename Manager::SpaceVec> positions{};
237 if (not sel_cfg[
"positions"]) {
238 throw KeyError(
"positions", sel_cfg,
239 "Could not select cells by positions!");
242 for (
const auto& pos_node : sel_cfg[
"positions"]) {
243 using SpaceVec =
typename Manager::SpaceVec;
244 using ET =
typename Manager::SpaceVec::elem_type;
246 const auto vec = pos_node.as<std::array<ET, Manager::dim>>();
252 for (
DimType i = 0; i < Manager::dim; i++) {
255 positions.push_back(pos);
258 return select_entities<SelectionMode::position>(mngr, positions);
262 const auto b = get_as<std::string>(
"boundary", sel_cfg);
263 return select_entities<SelectionMode::boundary>(mngr, b);
267 const auto num_v = get_as<unsigned>(
"num_vertical", sel_cfg);
268 const auto num_h = get_as<unsigned>(
"num_horizontal", sel_cfg);
272 auto perm = std::pair<double, double>{0., 0.};
273 auto perm_cfg = sel_cfg[
"permeability"];
274 if (perm_cfg and perm_cfg.IsSequence()) {
275 perm = perm_cfg.as<std::pair<double, double>>();
277 else if (perm_cfg and perm_cfg.IsMap()) {
278 perm.first = get_as<double>(
"horizontal", perm_cfg);
279 perm.second = get_as<double>(
"vertical", perm_cfg);
281 else if (perm_cfg and perm_cfg.IsScalar()) {
282 perm.first = perm_cfg.as<
double>();
283 perm.second = perm_cfg.as<
double>();
287 auto gate_width = std::pair<unsigned, unsigned>{0, 0};
288 auto gate_cfg = sel_cfg[
"gate_width"];
289 if (gate_cfg and gate_cfg.IsSequence()) {
290 gate_width = gate_cfg.as<std::pair<unsigned, unsigned>>();
292 else if (gate_cfg and gate_cfg.IsMap()) {
293 gate_width.first = get_as<unsigned>(
"horizontal", gate_cfg);
294 gate_width.second = get_as<unsigned>(
"vertical", gate_cfg);
296 else if (gate_cfg and gate_cfg.IsScalar()) {
297 gate_width.first = gate_cfg.as<
unsigned>();
298 gate_width.second = gate_cfg.as<
unsigned>();
301 return select_entities<SelectionMode::lanes>(mngr, num_v, num_h,
306 const auto p_seed = get_as<double>(
"p_seed", sel_cfg);
307 const auto p_attach = get_as<double>(
"p_attach", sel_cfg);
308 const auto num_passes = get_as<unsigned>(
"num_passes", sel_cfg);
313 select_entities<SelectionMode::clustered_simple>(mngr,
326 throw std::invalid_argument(
"The selection mode '"
328 "manager type or via the configuration!");
344 class Container = EntityContainer<typename Manager::Entity>,
345 class Condition = std::function<bool(
const std::shared_ptr<typename Manager::Entity>&)>,
346 typename std::enable_if_t<mode == SelectionMode::condition, int> = 0
349 const Condition& condition)
351 Container selected{};
352 std::copy_if(mngr.entities().begin(), mngr.entities().end(),
353 std::back_inserter(selected),
371 class Container = EntityContainer<typename Manager::Entity>,
372 typename std::enable_if_t<mode == SelectionMode::sample, int> = 0
375 const int num_entities)
377 if ( num_entities < 0
378 or
static_cast<std::size_t
>(num_entities) > mngr.entities().size())
380 throw std::invalid_argument(
"Argument num_entities need be in the "
381 "interval [0, entity container size]!");
384 Container selected{};
385 selected.reserve(num_entities);
388 std::sample(mngr.entities().begin(), mngr.entities().end(),
389 std::back_inserter(selected),
390 static_cast<std::size_t
>(num_entities), *mngr.rng());
407 class Container = EntityContainer<typename Manager::Entity>,
408 typename std::enable_if_t<mode == SelectionMode::probability, int> = 0
411 const double probability)
418 return mngr.entities();
420 else if (probability < 0. or probability > 1.) {
421 throw std::invalid_argument(
"Entity selection in mode 'probability' "
422 "failed due to probability argument outside of interval [0., 1.]");
426 std::uniform_real_distribution<> dist(0., 1.);
429 select_entities<SelectionMode::condition>(
432 [&, dist{std::move(dist)}](
const auto&)
mutable {
453 class Container = EntityContainer<typename Manager::Entity>,
454 typename std::enable_if_t<mode == SelectionMode::position, int> = 0
457 const std::vector<typename Manager::SpaceVec>& positions)
459 Container selected{};
460 for (
const auto& pos : positions) {
461 selected.push_back(mngr.cell_at(pos));
476 class Container = EntityContainer<typename Manager::Entity>,
477 typename std::enable_if_t<mode == SelectionMode::boundary, int> = 0
480 const std::string& boundary)
482 return mngr.boundary_cells(
boundary);
524 class Container = EntityContainer<typename Manager::Entity>,
525 typename std::enable_if_t<mode == SelectionMode::lanes, int> = 0
529 const unsigned int num_vertical,
530 const unsigned int num_horizontal,
531 const std::pair<double, double> permeability = {0., 0.},
532 const std::pair<unsigned int, unsigned int> gate_width = {0, 0}
535 static_assert(Manager::Space::dim == 2,
"Only 2D space is supported.");
536 using SpaceVec =
typename Manager::SpaceVec;
537 using MultiIndex =
typename Manager::MultiIndex;
540 const auto grid = mngr.grid();
541 const MultiIndex shape = grid->shape();
542 const auto num_cells = grid->num_cells();
543 const SpaceVec extent = grid->space()->extent;
544 const auto eff_resolution = grid->effective_resolution();
547 if (num_vertical >= shape[0] or num_horizontal >= shape[1]) {
548 throw std::invalid_argument(
"Given number of vertical and/or "
549 "horizontal lanes is equal or larger to the number of cells along "
550 "that dimension! Choose a smaller value.");
554 if (permeability.first < 0. or permeability.first > 1.) {
555 throw std::invalid_argument(fmt::format(
556 "Permeability in horizontal lanes needs to be in interval "
557 "[0., 1.], but was: {}", permeability.first)
560 if (permeability.second < 0. or permeability.second > 1.) {
561 throw std::invalid_argument(fmt::format(
562 "Permeability in vertical lanes needs to be in interval "
563 "[0., 1.], but was: {}", permeability.second)
569 "Selecting cells for lanes ...\n"
570 " num: {} horizontal, \t{} vertical\n"
571 " permeability: {} horizontal, \t{} vertical\n"
572 " gate width: {} horizontal, \t{} vertical\n",
573 num_horizontal, num_vertical,
574 permeability.first, permeability.second,
575 gate_width.first, gate_width.second
583 const auto num_lanes = MultiIndex{num_vertical, num_horizontal};
584 SpaceVec lane_start, lane_step;
586 if (grid->is_periodic()) {
588 lane_step = extent / num_lanes;
591 lane_start = extent / (num_lanes + 1);
592 lane_step = lane_start;
601 std::set<IndexType> indices_x, indices_y;
603 for (
unsigned int i = 0; i < num_vertical; i++) {
604 const SpaceVec proxy_pos = {lane_start[0] + i * lane_step[0], 0.};
605 indices_x.insert(grid->midx_of(grid->cell_at(proxy_pos))[0]);
608 for (
unsigned int i = 0; i < num_horizontal; i++) {
609 const SpaceVec proxy_pos = {0., lane_start[1] + i * lane_step[1]};
610 indices_y.insert(grid->midx_of(grid->cell_at(proxy_pos))[1]);
615 auto num_gates = num_lanes;
616 SpaceVec gate_start, gate_step;
617 const SpaceVec grid_step = 1./eff_resolution;
619 if (grid->is_periodic()) {
620 gate_step = extent / num_gates;
623 num_gates = num_gates + 1;
624 gate_step = extent / num_gates;
630 if (gate_width.first % 2 == 0) {
631 gate_start = gate_step / 2. - SpaceVec(
632 {grid_step[0] * (gate_width.first - 1) / 2.,
633 grid_step[1] * (gate_width.second - 1) / 2.});
636 gate_start = gate_step / 2. - SpaceVec(
637 {grid_step[0] * (gate_width.first) / 2.,
638 grid_step[1] * (gate_width.second) / 2.});
644 std::set<IndexType> gates_indices_x, gates_indices_y;
648 for (
unsigned int i = 0; i < num_gates[0]; i++) {
649 for (
unsigned int j = 0; j < gate_width.first; j++) {
650 proxy_pos = {gate_start[0] + i*gate_step[0] + j*grid_step[0],
652 gates_indices_x.insert(
653 grid->midx_of(grid->cell_at(proxy_pos))[0]
657 for (
unsigned int i = 0; i < num_gates[1]; i++) {
658 for (
unsigned int j = 0; j < gate_width.second; j++) {
660 gate_start[1] + i*gate_step[1] + j*grid_step[1]};
661 gates_indices_y.insert(
662 grid->midx_of(grid->cell_at(proxy_pos))[1]
667 catch (std::invalid_argument& exc) {
668 throw std::invalid_argument(fmt::format(
669 "Failed to determine gate cells for lane selection, presumably "
670 "because the gate width was chosen larger than the compartment "
671 "size. Check that the gate width (h: {:d}, v: {:d}) fits into the "
672 "compartment. Grid shape: ({:d} x {:d}, {}). "
673 "Number of lanes: (h: {:d}, v: {:d}).",
674 gate_width.first, gate_width.second,
676 grid->is_periodic() ?
"periodic" :
"non-periodic",
677 num_horizontal, num_vertical
683 std::vector<IndexType> selected_ids{};
686 std::uniform_real_distribution<> dist(0., 1.);
697 for (
IndexType cell_id = 0; cell_id < num_cells; cell_id++) {
698 const auto midx = grid->midx_of(cell_id);
702 if (indices_y.find(midx[1]) != indices_y.end()) {
704 if (gates_indices_x.find(midx[0]) != gates_indices_x.end()) {
708 else if (permeability.first > 0. and
709 dist(*mngr.rng()) < permeability.first)
715 selected_ids.push_back(cell_id);
718 else if (indices_x.find(midx[0]) != indices_x.end()) {
720 if (gates_indices_y.find(midx[1]) != gates_indices_y.end()) {
724 else if (permeability.second > 0. and
725 dist(*mngr.rng()) < permeability.second)
731 selected_ids.push_back(cell_id);
739 mngr.log()->debug(
"Selected {:d} / {:d} cells using mode 'lanes'.",
740 selected_ids.size(), mngr.cells().size());
743 return mngr.entity_pointers_from_ids(selected_ids);
764 class Container = EntityContainer<typename Manager::Entity>,
765 typename std::enable_if_t<mode == SelectionMode::clustered_simple, int> = 0
769 const double p_attach,
770 const unsigned int num_passes)
772 if (p_attach < 0. or p_attach > 1.) {
773 throw std::invalid_argument(
"Argument p_attach needs to be a "
774 "probability, i.e. be in interval [0., 1.]!");
777 mngr.log()->debug(
"Selecting cell clusters ... (p_seed: {}, p_attach: {}, "
778 "num_passes: {})", p_seed, p_attach, num_passes);
781 const auto seeds = select_entities<SelectionMode::probability>(mngr,
784 mngr.log()->debug(
"Selected {} clustering seeds.", seeds.size());
789 std::unordered_set<IndexType> selected_ids{};
790 std::unordered_set<IndexType> ids_to_attach{};
793 for (
const auto& cell : seeds) {
794 selected_ids.insert(cell->id());
798 std::uniform_real_distribution<> dist(0., 1.);
801 for (
unsigned int i = 0; i < num_passes; i++) {
807 ids_to_attach.clear();
808 for (
const auto cell_id : selected_ids) {
809 for (
const auto nb_id : mngr.grid()->neighbors_of(cell_id)) {
810 if (dist(*mngr.rng()) < p_attach) {
811 ids_to_attach.insert(nb_id);
817 selected_ids.insert(ids_to_attach.begin(), ids_to_attach.end());
819 mngr.log()->debug(
"Finished pass {}. Have {} cells selected now.",
820 i + 1, selected_ids.size());
825 mngr.entity_pointers_from_ids(
826 std::vector<IndexType>(selected_ids.begin(), selected_ids.end())
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
std::string selection_mode_to_string(const SelectionMode &mode)
Given a SelectionMode enum value, return the corresponding string key.
Definition: select.hh:135
SelectionMode
Possible selection modes; availability depends on choice of manager.
Definition: select.hh:72
const std::map< std::string, SelectionMode > selection_mode_map
A map from strings to Select enum values.
Definition: select.hh:116
Container select_entities(const Manager &mngr, const DataIO::Config &sel_cfg)
Select entities according to parameters specified in a configuration.
Definition: select.hh:200
@ 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.
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:163
static constexpr bool value
Definition: select.hh:164
Metafunction to determine whether a Manager is a CellManager.
Definition: select.hh:152
static constexpr bool value
Definition: select.hh:153