Utopia 2
Framework for studying models of complex & adaptive systems.
Loading...
Searching...
No Matches
Modules | Namespaces | Classes | Functions
Entity Selection

An interface to select a subset of entities from a manager. More...

Collaboration diagram for Entity Selection:

Modules

 SelectionModes
 

Namespaces

namespace  Utopia
 

Classes

struct  Utopia::is_cell_manager< M >
 Metafunction to determine whether a Manager is a CellManager. More...
 
struct  Utopia::is_agent_manager< M >
 Metafunction to determine whether a Manager is an AgentManager. More...
 

Functions

template<class Manager , class Container = EntityContainer<typename Manager::Entity>>
Container Utopia::select_entities (const Manager &mngr, const DataIO::Config &sel_cfg)
 Select entities according to parameters specified in a configuration.
 
template<SelectionMode mode, class Manager , class Container = EntityContainer<typename Manager::Entity>, class Condition = std::function<bool(const std::shared_ptr<typename Manager::Entity>&)>, typename std::enable_if_t< mode==SelectionMode::condition, int > = 0>
Container Utopia::select_entities (const Manager &mngr, const Condition &condition)
 Return a container with entities that match the given condition.
 
template<SelectionMode mode, class Manager , class Container = EntityContainer<typename Manager::Entity>, typename std::enable_if_t< mode==SelectionMode::sample, int > = 0>
Container Utopia::select_entities (const Manager &mngr, const int num_entities)
 Select a sample of entities randomly.
 
template<SelectionMode mode, class Manager , class Container = EntityContainer<typename Manager::Entity>, typename std::enable_if_t< mode==SelectionMode::probability, int > = 0>
Container Utopia::select_entities (const Manager &mngr, const double probability)
 Select entities with a certain probability.
 
template<SelectionMode mode, class Manager , class Container = EntityContainer<typename Manager::Entity>, typename std::enable_if_t< mode==SelectionMode::position, int > = 0>
Container Utopia::select_entities (const Manager &mngr, const std::vector< typename Manager::SpaceVec > &positions)
 Selects cells at given positions in space.
 
template<SelectionMode mode, class Manager , class Container = EntityContainer<typename Manager::Entity>, typename std::enable_if_t< mode==SelectionMode::boundary, int > = 0>
Container Utopia::select_entities (const Manager &mngr, const std::string &boundary)
 Selects cells on a boundary.
 
template<SelectionMode mode, class Manager , class Container = EntityContainer<typename Manager::Entity>, typename std::enable_if_t< mode==SelectionMode::lanes, int > = 0>
Container Utopia::select_entities (const Manager &mngr, const unsigned int num_vertical, const unsigned int num_horizontal, const std::pair< double, double > permeability={0., 0.}, const std::pair< unsigned int, unsigned int > gate_width={0, 0})
 Selects horizontal or vertical lanes of cells.
 
template<SelectionMode mode, class Manager , class Container = EntityContainer<typename Manager::Entity>, typename std::enable_if_t< mode==SelectionMode::clustered_simple, int > = 0>
Container Utopia::select_entities (const Manager &mngr, const double p_seed, const double p_attach, const unsigned int num_passes)
 Selects cells that are clustered using a simple clustering algorithm.
 

Detailed Description

An interface to select a subset of entities from a manager.

The Utopia constructs Utopia::Cell and Utopia::Agent both have a common parent type: Utopia::Entity. Additionally, there are corresponding manager types that provide an interface to work with these entities: Utopia::CellManager and Utopia::AgentManager.

Given this structure, Utopia is able to provide a common interface with which a subset of entities can be selected in a consistent and configurable fashion. This allows to re-use the selection algorithms and while also allowing specializations for certain entity types.

The interface requires managers of Utopia::Entity -derived types; let's call them EntityManagers for now. (Side note: The interface is currently only assumed; the CellManager and AgentManager are yet to be based on a common base class). These managers provide the embedding of a container of entities, which is required as soon as more complicated selections are to be performed, e.g. selecting cells that are distributed in a certain pattern in space.

For the mode of selection, the Utopia::SelectionMode is used. For each mode value, a Utopia::select_entities method is specialized. That method defines the parameters required for the selection. The whole interface is made accessible via configuration by the specialization that accepts a Utopia::DataIO::Config as argument. Furthermore, the entity managers can implement a method that makes use of the free functions defined here, for example Utopia::CellManager::select_entities .

This selection interface has a similar motivation as the Utopia::apply_rule interface; in fact, it cooperates nicely with that interface, as you can first select some entities and then apply an operation to them...

Function Documentation

◆ select_entities() [1/8]

template<SelectionMode mode, class Manager , class Container = EntityContainer<typename Manager::Entity>, class Condition = std::function<bool(const std::shared_ptr<typename Manager::Entity>&)>, typename std::enable_if_t< mode==SelectionMode::condition, int > = 0>
Container Utopia::select_entities ( const Manager &  mngr,
const Condition &  condition 
)

Return a container with entities that match the given condition.

Parameters
mngrThe manager to select the entities from
conditionA unary function working on a single entity and returning a boolean.
363{
364 Container selected{};
365 std::copy_if(mngr.entities().begin(), mngr.entities().end(),
366 std::back_inserter(selected),
367 condition);
368 return selected;
369}

◆ select_entities() [2/8]

template<class Manager , class Container = EntityContainer<typename Manager::Entity>>
Container Utopia::select_entities ( const Manager &  mngr,
const DataIO::Config sel_cfg 
)

Select entities according to parameters specified in a configuration.

Via the mode key, one of the modes (see Utopia::SelectionMode) can be selected. Depending on that mode, the other parameters are extracted from the configuration.

Available keys for each mode:

  • sample mode: num_cells
  • probability mode: probability
  • positions mode: positions (a list of coordinate pairs)
  • boundary mode: boundary (a string, e.g. left, right, top, bottom)
  • lanes mode: num_horizontal, num_vertical, permeability (optional; can be given as scalar, pair, or mapping w/ keys horizontal and vertical), gate_width (optional, same as permeability)
  • clustered_simple mode: p_seed, p_attach, num_passes
Note
The Utopia::SelectionMode::condition is not available!
Parameters
mngrThe manager to select the entities from
sel_cfgThe configuration node containing the expected key-value pairs specifying the selection.
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 }
226 const SelectionMode mode = selection_mode_map.at(mode_str);
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!
236 return select_entities<SelectionMode::sample>(mngr, N);
237 }
238
239 if (mode == SelectionMode::probability) {
240 const auto p = get_as<double>("probability", sel_cfg);
241 return select_entities<SelectionMode::probability>(mngr, p);
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
271 return select_entities<SelectionMode::position>(mngr, positions);
272 }
273
274 if (mode == SelectionMode::boundary) {
275 const auto b = get_as<std::string>("boundary", sel_cfg);
276 return select_entities<SelectionMode::boundary>(mngr, b);
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
314 return select_entities<SelectionMode::lanes>(mngr, num_v, num_h,
315 perm, gate_width);
316 }
317
318 if (mode == SelectionMode::clustered_simple) {
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
326 select_entities<SelectionMode::clustered_simple>(mngr,
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}
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

◆ select_entities() [3/8]

template<SelectionMode mode, class Manager , class Container = EntityContainer<typename Manager::Entity>, typename std::enable_if_t< mode==SelectionMode::clustered_simple, int > = 0>
Container Utopia::select_entities ( const Manager &  mngr,
const double  p_seed,
const double  p_attach,
const unsigned int  num_passes 
)

Selects cells that are clustered using a simple clustering algorithm.

This is done by first determining some "seed" cells and then attaching their neighbors to them with a certain probability.

Parameters
mngrThe manager to select the entities from. Needs to be a Utopia::CellManager for this function.
p_seedThe probability with which a cell becomes a seed for a cluster
p_attachThe probability with which a neighbor of a cluster cell is also added to the cluster
num_passesHow often to invoke the attachment iteration
Todo:
TODO Could generalize this to all kinds of entities with neighbors.
784{
785 if (p_attach < 0. or p_attach > 1.) {
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"
794 const auto seeds = select_entities<SelectionMode::probability>(mngr,
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}

◆ select_entities() [4/8]

template<SelectionMode mode, class Manager , class Container = EntityContainer<typename Manager::Entity>, typename std::enable_if_t< mode==SelectionMode::probability, int > = 0>
Container Utopia::select_entities ( const Manager &  mngr,
const double  probability 
)

Select entities with a certain probability.

Iterates over all entities and selects each entity with the given probability.

Note
The order of the entities in the returned container is the same as in the underlying container!
Parameters
mngrThe manager to select the entities from
probabilityThe probablity with which a single entity is selected
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 }
433 else if (probability < 0. or probability > 1.) {
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
442 select_entities<SelectionMode::condition>(
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}
@ probability
Select an entity with a given probability.

◆ select_entities() [5/8]

template<SelectionMode mode, class Manager , class Container = EntityContainer<typename Manager::Entity>, typename std::enable_if_t< mode==SelectionMode::sample, int > = 0>
Container Utopia::select_entities ( const Manager &  mngr,
const int  num_entities 
)

Select a sample of entities randomly.

Uses std::sample to select num_entities randomly.

Note
The order of the entities in the returned container is the same as in the underlying container!
Parameters
mngrThe manager to select the entities from
num_entitiesThe number of entities to sample from the given container. Need be a value in [0, container size]
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
397 Container selected{};
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}

◆ select_entities() [6/8]

template<SelectionMode mode, class Manager , class Container = EntityContainer<typename Manager::Entity>, typename std::enable_if_t< mode==SelectionMode::boundary, int > = 0>
Container Utopia::select_entities ( const Manager &  mngr,
const std::string &  boundary 
)

Selects cells on a boundary.

Invokes CellManager::boundary_cells

Parameters
mngrThe manager to select the entities from. Needs to be a Utopia::CellManager for this work.
boundaryWhich boundary to select.
494{
495 return mngr.boundary_cells(boundary);
496}

◆ select_entities() [7/8]

template<SelectionMode mode, class Manager , class Container = EntityContainer<typename Manager::Entity>, typename std::enable_if_t< mode==SelectionMode::position, int > = 0>
Container Utopia::select_entities ( const Manager &  mngr,
const std::vector< typename Manager::SpaceVec > &  positions 
)

Selects cells at given positions in space.

Invokes CellManager::cell_at to populate a container of cells that are at the given absolute positions. Which cells are selected depends on the chosen discretization, see Utopia::Grid and the derived classes.

Parameters
mngrThe manager to select the entities from. Needs to be a Utopia::CellManager for this work.
positionsAbsolute positions; the cells under these positions are then selected.
471{
472 Container selected{};
473 for (const auto& pos : positions) {
474 selected.push_back(mngr.cell_at(pos));
475 }
476 return selected;
477}

◆ select_entities() [8/8]

template<SelectionMode mode, class Manager , class Container = EntityContainer<typename Manager::Entity>, typename std::enable_if_t< mode==SelectionMode::lanes, int > = 0>
Container Utopia::select_entities ( const Manager &  mngr,
const unsigned int  num_vertical,
const unsigned int  num_horizontal,
const std::pair< double, double >  permeability = {0., 0.},
const std::pair< unsigned int, unsigned int >  gate_width = {0, 0} 
)

Selects horizontal or vertical lanes of cells.

The lanes are spaced such that the domain is divided into N equally large parts for periodic space and N+1 parts for non-periodic space in each dimension.

For example:

  • In non-periodic space, two vertical lanes will be set at 1/3 and 2/3 relative position of the space, thus dividing the domain into three parts in x-direction
  • In periodic space, one needs to take the wraparound into account. Two vertical lanes would then be set at the lower-value boundary and at the center of the grid and would divide the domain into two parts! For three lanes, they would be at 0/3, 1/3, and 2/3 of the relative space extent along the x-dimension.

Calculation occurs by first determining the relative position along the corresponding dimension at which a lane is to occur. From that, the multi- multi-index is computed, and then all cells that match the desired multi index component are selected to become part of the lane. This is done on the grid level, i.e.: on the level of multi indices. As all grid discretizations can operate on multi-indices, this approach is valid among all types of grid discretizations.

Parameters
mngrThe manager to select the entities from. Needs to be a Utopia::CellManager .
num_verticalNumber of vertical lanes
num_horizontalNumber of horizontal lanes
permeabilityThe permeability of the horizontal and vertical lanes, respectively. The probability that a cell is spared out. Optional.
gate_widthThe width of gates in the horizontal and vertical lanes, respectively. Given in number of cells per compartment, i.e. between two lanes. Optional.
544 {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",
586 num_horizontal, num_vertical,
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);
605 lane_step = lane_start;
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",
690 num_horizontal, num_vertical
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}