Utopia  2
Framework for studying models of complex & adaptive systems.
square.hh
Go to the documentation of this file.
1 #ifndef UTOPIA_CORE_GRIDS_SQUARE_HH
2 #define UTOPIA_CORE_GRIDS_SQUARE_HH
3 
4 #include <cmath>
5 #include <sstream>
6 #include <algorithm>
7 
8 #include "base.hh"
9 
10 namespace Utopia {
17 
28 template<class Space>
30  : public Grid<Space>
31 {
32 public:
34  using Base = Grid<Space>;
35 
37  static constexpr DimType dim = Space::dim;
38 
40  using SpaceVec = typename Space::SpaceVec;
41 
44 
47 
48 
49 private:
50  // -- SquareGrid-specific members -----------------------------------------
53 
56 
57 
58 public:
59  // -- Constructors --------------------------------------------------------
61 
64  SquareGrid (std::shared_ptr<Space> space, const Config& cfg)
65  :
66  Base(space, cfg),
69  {
70  if constexpr (dim > 1) {
71  // Make sure the cells really are square
72  const SpaceVec eff_res = effective_resolution();
73 
74  if (eff_res.min() != eff_res.max()) {
75  // Format effective resolution to show it in error message
76  std::stringstream efr_ss;
77  efr_ss << eff_res;
78 
79  throw std::invalid_argument("Given the extent of the physical "
80  "space and the specified resolution, a mapping with "
81  "exactly square cells could not be found! Either adjust "
82  "the physical space, the resolution of the grid, or "
83  "choose another grid. Effective resolution was:\n"
84  + efr_ss.str() + ", but should be the same in all "
85  "dimensions!");
86  }
87  }
88  }
89 
90  // -- Implementations of virtual base class functions ---------------------
91  // .. Number of cells & shape .............................................
92 
94 
96  IndexType num_cells() const override {
97  return std::accumulate(this->_shape.begin(), this->_shape.end(),
98  1, std::multiplies<IndexType>());
99  };
100 
102 
105  SpaceVec effective_resolution() const override {
106  // Use element-wise division by the physical extent (double)
107  return _shape / this->_space->extent;
108  }
109 
111  MultiIndex shape() const override {
112  return _shape;
113  }
114 
116  GridStructure structure() const override {
117  return GridStructure::square;
118  }
119 
120 
121  // .. Position-related methods ............................................
123 
125  MultiIndex midx_of(const IndexType id) const override {
126  static_assert(dim <= 2, "MultiIndex only implemented for 1D and 2D!");
127 
128  if constexpr (dim == 1) {
129  return MultiIndex({id % _shape[0]});
130  }
131  else {
132  return MultiIndex({id % _shape[0],
133  id / _shape[0]});
134  }
135  }
136 
138 
140  SpaceVec barycenter_of(const IndexType id) const override {
141  // Offset on grid + offset within cell
142  return (midx_of(id) % _cell_extent) + (_cell_extent/2.);
143  // NOTE The %-operator performs element-wise multiplication
144  }
145 
147 
149  SpaceVec extent_of(const IndexType) const override {
150  return _cell_extent;
151  }
152 
154 
160  std::vector<SpaceVec> vertices_of(const IndexType id) const override {
161  static_assert(dim == 2,
162  "SquareGrid::vertices_of is only implemented for 2D!");
163 
164  std::vector<SpaceVec> vertices{};
165  vertices.reserve(4);
166 
167  // NOTE The %-operator performs element-wise multiplication
168  // Counter-clockwise, starting bottom left ...
169  vertices.push_back(midx_of(id) % _cell_extent);
170  vertices.push_back(vertices[0] + _cell_extent % SpaceVec({1., 0.}));
171  vertices.push_back(vertices[0] + _cell_extent);
172  vertices.push_back(vertices[0] + _cell_extent % SpaceVec({0., 1.}));
173 
174  return vertices;
175  }
176 
178 
190  IndexType cell_at(const SpaceVec& pos) const override {
191  static_assert(dim == 2,
192  "SquareGrid::cell_at only implemented for 2D!");
193 
194  // The multi-index element type to use for static casts
195  using midx_et = typename MultiIndex::elem_type;
196 
197  // The multi-index to be calculated
198  MultiIndex midx;
199 
200  // Distinguish periodic and non-periodic case
201  // NOTE While there is some duplicate code, this is the configuration
202  // with the least amount of unnecessary / duplicate value checks.
203  if (this->is_periodic()) {
204  // Calculate the real-valued position in units of cell extents,
205  // using the position mapped back into space. That function takes
206  // care to map the high-value boundary to the low-value boundary.
207  const SpaceVec ridx = ( this->_space->map_into_space(pos)
208  / _cell_extent);
209 
210  // Can now calculate the integer multi index, rounding down
211  midx = {static_cast<midx_et>(ridx[0]),
212  static_cast<midx_et>(ridx[1])};
213  }
214  else {
215  // Make sure the given coordinate is actually within the space
216  if (not this->_space->contains(pos)) {
217  throw std::invalid_argument("The given position is outside "
218  "the non-periodic space associated with this grid!");
219  }
220 
221  // Calculate the real-valued position in units of cell extents
222  const SpaceVec ridx = pos / _cell_extent;
223 
224  // Calculate the integer multi index, rounding down
225  midx = {static_cast<midx_et>(ridx[0]),
226  static_cast<midx_et>(ridx[1])};
227 
228  // Associate points on high-value boundaries with boundary cells
229  if (midx[0] == _shape[0]) {
230  midx[0]--;
231  }
232  if (midx[1] == _shape[1]) {
233  midx[1]--;
234  }
235  // Note that with _shape having only elements >= 1, the decrement
236  // does not cause any issues.
237  }
238 
239  // From the multi index, calculate the corresponding ID
240  return midx[0] + (midx[1] * _shape[0]);
241  // Equivalent to:
242  // midx[0] * id_shift<0>()
243  // + midx[1] * id_shift<1>()
244  }
245 
247 
257  std::set<IndexType> boundary_cells(std::string select="all") const override
258  {
259  static_assert(dim <= 2,
260  "SquareGrid::boundary_cells only implemented for 1D and 2D!");
261 
262  // else: non-periodic space
263 
264  // The target set all IDs are to be emplaced in
265  std::set<IndexType> bc_ids;
266 
267  // Distinguish by dimensionality of the space
268  if constexpr (dim == 1) {
269  if (select != "all" and select != "left" and select != "right") {
270  throw std::invalid_argument("Invalid value for argument "
271  "`select` in call to method SquareGrid::boundary_cells! "
272  "Available arguments (for currently selected "
273  "dimensionality) are: "
274  "'all', 'left', 'right'. Given value: '" + select + "'");
275  }
276  // For periodic space, this is easy:
277  if (this->is_periodic()) {
278  return {};
279  }
280 
281  // Left boundary (consists only of one cell)
282  if (select == "all" or select == "left") {
283  bc_ids.emplace(0);
284  }
285 
286  // Right boundary (also consists only of one cell)
287  if (select == "all" or select == "right") {
288  bc_ids.emplace_hint(bc_ids.end(), _shape[0] - 1);
289  }
290  }
291  else if constexpr (dim == 2) {
292  if ( select != "all"
293  and select != "left" and select != "right"
294  and select != "bottom" and select != "top")
295  {
296  throw std::invalid_argument("Invalid value for argument "
297  "`select` in call to method SquareGrid::boundary_cells! "
298  "Available arguments (for currently selected "
299  "dimensionality) are: "
300  "'all', 'left', 'right', 'bottom', 'top'. Given value: '"
301  + select + "'");
302  }
303 
304  // For periodic space, this is easy:
305  if (this->is_periodic()) {
306  return {};
307  }
308 
309  // NOTE It is important to use the hinting features of std::set
310  // here, which allow to run the following in amortized
311  // constant time instead of logarithmic with set size.
312  // Below, it always makes sense to hint at inserting right
313  // before the end.
314  // Hint for the first element needs to be the beginning
315  auto hint = bc_ids.begin();
316 
317  // Bottom boundary (lowest IDs)
318  if (select == "all" or select == "bottom") {
319  // 0, ..., _shape[0] - 1
320  for (DistType id = 0; id < _shape[0]; id++) {
321  bc_ids.emplace_hint(hint, id);
322  hint = bc_ids.end();
323  }
324  }
325 
326  // Left boundary
327  if (select == "left") {
328  // First IDs in _shape[1] rows: 0, _shape[0], 2*_shape[0], ...
329  for (DistType row = 0; row < _shape[1]; row++) {
330  bc_ids.emplace_hint(hint, row * _shape[0]);
331  hint = bc_ids.end();
332  }
333  }
334 
335  // Right boundary
336  if (select == "right") {
337  // Last IDs in _shape[1] rows
338  const auto offset = _shape[0] - 1;
339 
340  for (DistType row = 0; row < _shape[1]; row++) {
341  bc_ids.emplace_hint(hint, offset + row * _shape[0]);
342  hint = bc_ids.end();
343  }
344  }
345 
346  // Left AND right (only for 'all' case, allows better hints)
347  if (select == "all") {
348  // First and last IDs in _shape[1] rows
349  const auto offset = _shape[0] - 1;
350 
351  for (DistType row = 0; row < _shape[1]; row++) {
352  // Left boundary cell
353  bc_ids.emplace_hint(hint, row * _shape[0]);
354 
355  // Right boundary cell (higher than left cell ID)
356  bc_ids.emplace_hint(bc_ids.end(),
357  offset + row * _shape[0]);
358 
359  hint = bc_ids.end();
360  }
361  }
362 
363  // Top boundary (highest IDs)
364  if (select == "all" or select == "top") {
365  // _shape[0] * (_shape[1]-1), ..., _shape[0] * _shape[1] - 1
366  for (DistType id = _shape[0] * (_shape[1]-1);
367  id < _shape[0] * _shape[1]; id++)
368  {
369  bc_ids.emplace_hint(hint, id);
370  hint = bc_ids.end();
371  }
372  }
373  }
374 
375  return bc_ids;
376  }
377 
378 
379 private:
380  // -- Helper functions ----------------------------------------------------
382 
390 
391  for (DimType i = 0; i < dim; i++) {
392  shape[i] = this->_space->extent[i] * this->_resolution;
393  }
394 
395  return shape;
396  }
397 
398 
399 protected:
400  // -- Neighborhood interface ----------------------------------------------
401 
404  const Config& nb_params) override
405  {
406  if (nb_mode == NBMode::empty) {
407  return this->_nb_empty;
408  }
409  else if (nb_mode == NBMode::vonNeumann) {
411  }
412  else if (nb_mode == NBMode::Moore) {
414  }
415  else {
416  throw std::invalid_argument("No '" + nb_mode_to_string(nb_mode)
417  + "' available for rectangular grid discretization!");
418  }
419  }
420 
421 
422  // .. Neighborhood implementations ........................................
423  // NOTE With C++20, the below lambdas would allow template arguments
424 
425  // .. von Neumann . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
426 
428 
442  // Extract the optional distance parameter
443  constexpr bool check_shape = true;
444  const auto distance = get_nb_param_distance<check_shape>(nb_params);
445 
446  // For distance 1, use the specialized functions which are defined as
447  // class members (to don't bloat this method even more). Those
448  // functions do not require any calculation or capture that goes beyond
449  // the capture of ``this``.
450  if (distance <= 1) {
451  if (this->is_periodic()) {
453  }
454  else {
456  }
457  }
458  // else: distance is > 1. Depending on periodicity of the grid, define
459  // the relevant lambda and let it capture as many values as possible in
460  // order to avoid recomputation.
461 
462  // Compute neighborhood size to allow it to be captured
464  nb_params);
465 
466  if (this->is_periodic()) {
467  return [this, distance, nb_size](const IndexType root_id){
468  static_assert((dim >= 1 and dim <= 2),
469  "VonNeumann neighborhood is implemented only for 1D or 2D "
470  "space!");
471 
472  // Instantiate container in which to store the neighboring IDs
473  IndexContainer neighbor_ids{};
474 
475  // Pre-allocating space brings factor ~2 speed improvement
476  neighbor_ids.reserve(nb_size);
477 
478  // Depending on the number of dimensions, add the IDs of
479  // neighboring cells in those dimensions.
480  // Add neighbors in dimension 1
481  for (DistType dist=1; dist <= distance; ++dist) {
482  add_low_val_neighbor_in_<0, true>(root_id,
483  dist,
484  neighbor_ids);
485  add_high_val_neighbor_in_<0, true>(root_id,
486  dist,
487  neighbor_ids);
488  }
489 
490  // If the dimension exists, add neighbors in dimension 2
491  if constexpr (dim >= 2) {
492  // Go through all the previously added neighbors and add
493  // the additional neighbors from the other dimension.
494  // NOTE that this algorithm requires the neighbors nearest
495  // to the root_id to have been pushed to the vector
496  // first. The fixed ordering of the previous addition
497  // is required.
498  const auto nb_size = neighbor_ids.size();
499 
500  for (DistType i=0; i < nb_size; ++i) {
501  // Add all neighbor ids up to the maximal distance
502  // along the dimension 2.
503  for (DistType dist = 1;
504  dist <= distance - 1 - i/2 + (i%2)/2;
505  ++dist)
506  {
507  // front neighbor
508  add_low_val_neighbor_in_<1, true>(neighbor_ids[i],
509  dist,
510  neighbor_ids);
511 
512  // back neighbor
513  add_high_val_neighbor_in_<1, true>(neighbor_ids[i],
514  dist,
515  neighbor_ids);
516  }
517  }
518 
519  // Finally, add the root cell's neighbors in the 2nd dim.
520  for (DistType dist=1; dist <= distance; ++dist) {
521  add_low_val_neighbor_in_<1, true>(root_id,
522  dist,
523  neighbor_ids);
524  add_high_val_neighbor_in_<1,true>(root_id,
525  dist,
526  neighbor_ids);
527  }
528  }
529 
530  // Return the container of cell indices
531  return neighbor_ids;
532  }; // End of lambda definition for periodic space
533  }
534 
535  else { // Space is non-periodic
536  return [this, distance, nb_size](const IndexType root_id){
537  static_assert(((dim == 1) or (dim == 2)),
538  "VonNeumann neighborhood is implemented only for 1D or 2D "
539  "space!");
540 
541  // Instantiate containers in which to store the neighboring IDs
542  IndexContainer front_nb_ids{};
543  IndexContainer back_nb_ids{};
544 
545  // Pre-allocating space brings a factor ~2 speed improvement
546  // NOTE The front_nb_ids vector needs to reserve memory for all
547  // neighbors including the back neighbors because these
548  // will be added to the container directly before
549  // returning it.
550  front_nb_ids.reserve(nb_size);
551  back_nb_ids.reserve(nb_size / 2);
552 
553  // Depending on the number of dimensions, add the IDs of
554  // neighboring cells in those dimensions.
555  // Add front neighbors in dimension 1
556  for (DistType dist=1; dist <= distance; ++dist) {
557  add_low_val_neighbor_in_<0, false>(root_id,
558  dist,
559  front_nb_ids);
560  add_high_val_neighbor_in_<0, false>(root_id,
561  dist,
562  back_nb_ids);
563  }
564 
565  // If the dimension exists, add neighbors in dimension 2
566  if constexpr (dim >= 2) {
567  // Go through the front neighbor ids in dimension 1 and add
568  // the neighbor ids in dimension 2
569  // NOTE that this algorithm requires the neighbors nearest
570  // to the root_id to have been pushed to the vector
571  // first. The fixed ordering of the previous addition
572  // is required.
573  const DistType front_nb_size = front_nb_ids.size();
574 
575  for (DistType i=0; i < front_nb_size; ++i) {
576  // Add all front neighbor ids up to the maximal
577  // distance along dimension 2.
578  for (DistType dist = 1;
579  dist <= distance - (i + 1);
580  ++dist)
581  {
582  // add front neighbors ids in dimension 2
583  add_low_val_neighbor_in_<1, false>(front_nb_ids[i],
584  dist,
585  front_nb_ids);
586 
587  // add back neighbors ids in dimension 2
588  add_high_val_neighbor_in_<1,false>(front_nb_ids[i],
589  dist,
590  front_nb_ids);
591  }
592  }
593 
594  // Go through the front neighbor ids in dimension 1 and add
595  // the neighbor ids in dimension 2
596  // NOTE that this algorithm requires the neighbors nearest
597  // to the root_id to have been pushed to the vector
598  // first. The fixed ordering of the previous addition
599  // is required.
600  const DistType back_nb_size = back_nb_ids.size();
601 
602  for (DistType i=0; i<back_nb_size; ++i) {
603  // Add all neighbor ids up to the maximal distance
604  // along second dimension
605  for (DistType dist = 1;
606  dist <= distance - (i + 1);
607  ++dist)
608  {
609  add_low_val_neighbor_in_<1, false>(back_nb_ids[i],
610  dist,
611  back_nb_ids);
612 
613  add_high_val_neighbor_in_<1, false>(back_nb_ids[i],
614  dist,
615  back_nb_ids);
616  }
617  }
618 
619  // Finally, add the root cell's neighbors in the 2nd dim.
620  for (DistType dist=1; dist <= distance; ++dist) {
621  add_low_val_neighbor_in_<1, false>(root_id,
622  dist,
623  front_nb_ids);
624 
625  add_high_val_neighbor_in_<1, false>(root_id,
626  dist,
627  back_nb_ids);
628  }
629  }
630 
631  // Combine the front and back neighbors container
632  front_nb_ids.insert(front_nb_ids.end(),
633  back_nb_ids.begin(), back_nb_ids.end());
634 
635  // Done now. The front neighbor container contains all the IDs.
636  return front_nb_ids;
637  }; // End of lambda definition for non-periodic space
638  }
639  }
640 
641 
644  [this](const IndexType root_id)
645  {
646  static_assert((dim >= 1 and dim <= 2),
647  "VonNeumann neighborhood is implemented only for 1D or 2D space!");
648 
649  // Instantiate container in which to store the neighboring cell IDs
650  IndexContainer neighbor_ids{};
651 
652  // The number of neighbors is known; pre-allocating space brings a
653  // speed improvement of about factor 2.
654  neighbor_ids.reserve(2 * dim);
655 
656  // Depending on the number of dimensions, add the IDs of neighboring
657  // cells in those dimensions
658  add_neighbors_in_<0, true>(root_id, neighbor_ids);
659 
660  if constexpr (dim >= 2) {
661  add_neighbors_in_<1, true>(root_id, neighbor_ids);
662  }
663 
664  // Return the container of cell indices
665  return neighbor_ids;
666  };
667 
668 
671  [this](const IndexType root_id)
672  {
673  static_assert(((dim == 1) or (dim == 2)),
674  "VonNeumann neighborhood is only implemented in 1 or 2 dimensions "
675  "in space!");
676 
677  // Instantiate container in which to store the neighboring cell IDs
678  IndexContainer neighbor_ids{};
679 
680  // The number of neighbors is known; pre-allocating space brings a
681  // speed improvement of about factor 2
682  neighbor_ids.reserve(2 * dim);
683 
684  // Depending on the number of dimensions, add the IDs of neighboring
685  // cells in those dimensions
686  add_neighbors_in_<0, false>(root_id, neighbor_ids);
687 
688  if constexpr (dim >= 2) {
689  add_neighbors_in_<1, false>(root_id, neighbor_ids);
690  }
691 
692  // Return the container of cell indices
693  return neighbor_ids;
694  };
695 
696 
697  // .. Moore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
698 
700 
714  // Extract the optional distance parameter
715  constexpr bool check_shape = true;
716  const auto distance = get_nb_param_distance<check_shape>(nb_params);
717 
718  // For distance 1, use the specialized functions which are defined as
719  // class members (to don't bloat this method even more). Those
720  // functions do not require any calculation or capture that goes beyond
721  // the capture of ``this``.
722  if (distance <= 1) {
723  if (this->is_periodic()) {
724  return _nb_Moore_periodic;
725  }
726  else {
727  return _nb_Moore_nonperiodic;
728  }
729  }
730  // else: distance is > 1. Depending on periodicity of the grid, define
731  // the relevant lambda and let it capture as many values as possible in
732  // order to avoid recomputation.
733 
734  // Compute neighborhood size to allow it to be captured
736  nb_params);
737 
738  if (this->is_periodic()) {
739  return [this, distance, nb_size](const IndexType root_id){
740  static_assert(dim == 2,
741  "Moore neighborhood is only available in 2D!");
742 
743  // Generate vector in which to store the neighbors...
744  IndexContainer neighbor_ids{};
745 
746  // ... and allocate space
747  neighbor_ids.reserve(nb_size);
748 
749  // Get all neighbors in the first dimension
750  for (DistType dist=1; dist <= distance; ++dist) {
751  add_low_val_neighbor_in_<0, true>(root_id, dist,
752  neighbor_ids);
753  add_high_val_neighbor_in_<0, true>(root_id, dist,
754  neighbor_ids);
755  }
756 
757  // For these neighbors, add _their_ neighbors in the second dimension
758  for (const auto& nb : neighbor_ids) {
759  for (DistType dist=1; dist <= distance; ++dist) {
760  add_low_val_neighbor_in_<1, true>(nb, dist,
761  neighbor_ids);
762  add_high_val_neighbor_in_<1, true>(nb, dist,
763  neighbor_ids);
764  }
765  }
766 
767  // And finally, add the root cell's neighbors in the second dimension
768  for (DistType dist=1; dist <= distance; ++dist) {
769  add_low_val_neighbor_in_<1, true>(root_id, dist,
770  neighbor_ids);
771  add_high_val_neighbor_in_<1, true>(root_id, dist,
772  neighbor_ids);
773  }
774 
775  return neighbor_ids;
776  }; // End of lambda definition for periodic space
777  }
778  else { // Space is non-periodic
779  return [this, distance, nb_size](const IndexType root_id){
780  static_assert(dim == 2,
781  "Moore neighborhood is only available in 2D!");
782 
783  // Generate vector in which to store the neighbors...
784  IndexContainer neighbor_ids{};
785 
786  // ... and allocate space
787  neighbor_ids.reserve(nb_size);
788 
789  // Get all neighbors in the first dimension
790  for (DistType dist=1; dist <= distance; ++dist) {
791  add_low_val_neighbor_in_<0, false>(root_id, dist,
792  neighbor_ids);
793  add_high_val_neighbor_in_<0, false>(root_id, dist,
794  neighbor_ids);
795  }
796 
797  // For these neighbors, add _their_ neighbors in the second dimension
798  for (const auto& nb : neighbor_ids) {
799  for (DistType dist=1; dist <= distance; ++dist) {
800  add_low_val_neighbor_in_<1, false>(nb, dist,
801  neighbor_ids);
802  add_high_val_neighbor_in_<1, false>(nb, dist, neighbor_ids);
803  }
804  }
805 
806  // And finally, add the root cell's neighbors in the second dimension
807  for (DistType dist=1; dist <= distance; ++dist) {
808  add_low_val_neighbor_in_<1, false>(root_id, dist,
809  neighbor_ids);
810  add_high_val_neighbor_in_<1, false>(root_id, dist,
811  neighbor_ids);
812  }
813 
814  return neighbor_ids;
815  }; // End of lambda definition for non-periodic space
816  }
817  }
818 
821  [this](const IndexType root_id)
822  {
823  static_assert(dim == 2, "Moore neighborhood is only available in 2D!");
824 
825  // Generate vector in which to store the neighbors and allocate space
826  IndexContainer neighbor_ids{};
827  neighbor_ids.reserve(8);
828 
829  // Get the neighbors in the second dimension
830  add_neighbors_in_<1, true>(root_id, neighbor_ids);
831  // ...have these neighbors at indices 0 and 1 now.
832 
833  // For these neighbors, add _their_ neighbors in the first dimension
834  add_neighbors_in_<0, true>(neighbor_ids[0], neighbor_ids);
835  add_neighbors_in_<0, true>(neighbor_ids[1], neighbor_ids);
836 
837  // And finally, add the root cell's neighbors in the first dimension
838  add_neighbors_in_<0, true>(root_id, neighbor_ids);
839 
840  return neighbor_ids;
841  };
842 
845  [this](const IndexType root_id)
846  {
847  static_assert(dim == 2, "Moore neighborhood is only available in 2D!");
848 
849  // Generate vector in which to store the neighbors and allocate space
850  IndexContainer neighbor_ids{};
851  neighbor_ids.reserve(8);
852 
853  // Get the neighbors in the second dimension
854  add_neighbors_in_<1, false>(root_id, neighbor_ids);
855  // root not at border: have them at indices 0 and 1 now
856  // root at border: less than two neighbors were added
857 
858  // Distinguish these two cases
859  if (neighbor_ids.size() == 2) {
860  // Was not at a boundary.
861  add_neighbors_in_<0, false>(neighbor_ids[0], neighbor_ids);
862  add_neighbors_in_<0, false>(neighbor_ids[1], neighbor_ids);
863  }
864  else if (neighbor_ids.size() == 1) {
865  // Was at a front XOR back boundary in dimension 2
866  add_neighbors_in_<0, false>(neighbor_ids[0], neighbor_ids);
867  }
868  // else: was at front AND back boundary (single row of cells in dim 2)
869 
870  // Finally, add the root's neighbors in the first dimension
871  add_neighbors_in_<0, false>(root_id, neighbor_ids);
872 
873  return neighbor_ids;
874  };
875 
876  // .. Neighborhood helper functions .......................................
877 
879 
890  template<DimType axis>
891  constexpr IndexType id_shift() const {
892  if constexpr (axis == 0) {
893  // End of recursion
894  return 1;
895  }
896  else {
897  // Recursive branch
898  return _shape[axis - 1] * id_shift<axis-1>();
899  }
900  }
901 
903 
919  template<DimType axis, bool periodic>
920  void add_neighbors_in_(const IndexType root_id,
921  IndexContainer& neighbor_ids) const
922  {
923  // Assure the number of dimensions is supported
924  static_assert(dim <= 2,
925  "Unsupported dimensionality of underlying space! Need be 1 or 2.");
926  static_assert(axis < dim);
927 
928  // Compute a "normalized" ID along the desired dimension in which the
929  // neighbors are to be added. Is always in [0, shape[d] - 1].
930  const auto nrm_id = ( (root_id % id_shift<axis+1>())
931  / id_shift<axis>());
932  // NOTE _Should_ also work for d > 2, but need tests for that.
933 
934  // Check if at low value boundary
935  if (nrm_id == 0) {
936  if constexpr (periodic) {
937  neighbor_ids.push_back( root_id
938  - id_shift<axis>()
939  + id_shift<axis+1>());
940  }
941  // else: not periodic; nothing to add here
942  }
943  else {
944  // Not at boundary; no need for the correction term
945  neighbor_ids.push_back(root_id - id_shift<axis>());
946  }
947 
948  // Check if at high value boundary
949  if (nrm_id == _shape[axis] - 1) {
950  if constexpr (periodic) {
951  neighbor_ids.push_back( root_id
952  + id_shift<axis>()
953  - id_shift<axis+1>());
954  }
955  }
956  else {
957  neighbor_ids.push_back(root_id + id_shift<axis>());
958  }
959  }
960 
962 
979  template<DimType axis, bool periodic>
981  const DistType distance,
982  IndexContainer& neighbor_ids) const
983  {
984  // Assure the number of dimensions is supported
985  static_assert(dim <= 2,
986  "Unsupported dimensionality of underlying space! Need be 1 or 2.");
987  static_assert(axis < dim);
988 
989  // If the distance is zero, no neighbor can be added; return nothing.
990  if (distance == 0) {
991  return;
992  }
993 
994  // Check if the neighbor to be added would pass a low value boundary.
995  // Do so by computing a "normalized" ID along the desired dimension in
996  // which the neighbor is to be added (always in [0, shape[d] - 1]) and
997  // then compare to the distance
998  if ( (root_id % id_shift<axis+1>()) / id_shift<axis>()
999  < distance)
1000  {
1001  if constexpr (periodic) {
1002  neighbor_ids.push_back( root_id
1003  - distance * id_shift<axis>()
1004  + id_shift<axis+1>());
1005  }
1006  }
1007  else {
1008  neighbor_ids.push_back( root_id
1009  - distance * id_shift<axis>());
1010  }
1011  }
1012 
1014 
1031  template<DimType axis, bool periodic>
1033  const DistType distance,
1034  IndexContainer& neighbor_ids) const
1035  {
1036  // Assure the number of dimensions is supported
1037  static_assert(dim <= 2,
1038  "Unsupported dimensionality of underlying space! Need be 1 or 2.");
1039  static_assert(axis < dim);
1040 
1041  // If the distance is zero, no neighbor can be added; return nothing.
1042  if (distance == 0) {
1043  return;
1044  }
1045 
1046  // Check if the neighbor to be added would pass a high value boundary.
1047  // Do so by computing a "normalized" ID along the desired dimension in
1048  // which the neighbor is to be added (always in [0, shape[d] - 1]) and
1049  // then compare to the distance from the high value boundary.
1050  if ( (root_id % id_shift<axis+1>()) / id_shift<axis>()
1051  >= _shape[axis] - distance)
1052  {
1053  if constexpr (periodic) {
1054  neighbor_ids.push_back( root_id
1055  + distance * id_shift<axis>()
1056  - id_shift<axis+1>());
1057  }
1058  }
1059  else {
1060  neighbor_ids.push_back( root_id
1061  + distance * id_shift<axis>());
1062  }
1063  }
1064 
1066 
1091  const Config& nb_params) const override
1092  {
1093  // Distinguish by mode
1094  if (nb_mode == NBMode::empty) {
1095  return 0;
1096  }
1097  else if (nb_mode == NBMode::Moore) {
1098  // Neighborhood size depends on the distance parameter. Get it.
1099  const auto distance = get_nb_param_distance(nb_params);
1100 
1101  if (distance <= 1) {
1102  return std::pow(2 + 1, dim) - 1;
1103  }
1104  else {
1105  return std::pow(2 * distance + 1, dim) - 1;
1106  }
1107  }
1108  else if (nb_mode == NBMode::vonNeumann) {
1109  // Neighborhood size depends on the distance parameter. Get it.
1110  const auto distance = get_nb_param_distance(nb_params);
1111 
1112  // This one is more complicated ...
1113  // Define a lambda that can be called recursively
1114  auto num_nbs_impl = [](const unsigned short int d, // dimension
1115  DistType distance,
1116  auto& num_nbs_ref)
1117  {
1118  if (d == 1) {
1119  // End of recursion
1120  return 2 * distance;
1121  }
1122  else {
1123  // Recursive branch
1124  DistType cnt = 0;
1125  while (distance > 0) {
1126  cnt += 2 * num_nbs_ref(d-1, distance, num_nbs_ref);
1127  --distance;
1128  }
1129  return cnt;
1130  }
1131  };
1132 
1133  // Call the recursive lambda with the space's dimensionality.
1134  return num_nbs_impl(this->dim, distance, num_nbs_impl);
1135  }
1136  else {
1137  throw std::invalid_argument("No '" + nb_mode_to_string(nb_mode)
1138  + "' available for rectangular grid discretization!");
1139  }
1140  };
1141 
1142 
1143  // .. Neighborhood parameter extraction helpers ...........................
1144 
1146 
1158  template<bool check_shape=false>
1159  DistType get_nb_param_distance(const Config& params) const {
1160  const auto distance = get_as<DistType>("distance", params, 1);
1161 
1162  // Check the value is smaller than the grid shape. It needs to fit into
1163  // the shape of the grid, otherwise all the algorithms above would have
1164  // to check for duplicate entries and be set-based, which would be
1165  // very inefficient.
1166  if constexpr (check_shape) {
1167  if ( this->is_periodic()
1168  and (distance * 2 + 1 > this->shape().min()))
1169  {
1170  // To inform about the grid shape, print it to the stringstream
1171  // and include it in the error message below.
1172  std::stringstream shape_ss;
1173  this->shape().print(shape_ss, "Grid Shape:");
1174 
1175  throw std::invalid_argument("The grid shape is too small to "
1176  "accomodate a neighborhood with 'distance' parameter set "
1177  "to " + get_as<std::string>("distance", params, "1")
1178  + " in a periodic space!\n" + shape_ss.str());
1179  }
1180  }
1181 
1182  return distance;
1183  }
1184 };
1185 
1186 // end group CellManager
1191 } // namespace Utopia
1192 
1193 #endif // UTOPIA_CORE_GRIDS_SQUARE_HH
The base class for all grid discretizations used by the CellManager.
Definition: base.hh:99
const DistType _resolution
How many cells to place per length unit of space.
Definition: base.hh:127
bool is_periodic() const
Whether the space this grid maps to is periodic.
Definition: base.hh:304
const NBMode & nb_mode() const
Const reference to the currently selected neighborhood mode.
Definition: base.hh:198
const std::shared_ptr< Space > _space
The space that is to be discretized.
Definition: base.hh:120
const Config & nb_params() const
The neighborhood parameters of the currently selected neighborhood.
Definition: base.hh:203
NBFuncID< Self > _nb_empty
A neighborhood function for empty neighborhood.
Definition: base.hh:323
const std::shared_ptr< Space > & space() const
Const reference to the space this grid maps to.
Definition: base.hh:299
auto nb_size() const
Maximum size of the currently selected neighborhood.
Definition: base.hh:208
DataIO::Config Config
The configuration type.
Definition: base.hh:114
A grid discretization using square cells.
Definition: square.hh:31
GridStructure structure() const override
Structure of the grid.
Definition: square.hh:116
IndexType num_cells() const override
Number of square cells required to fill the physical space.
Definition: square.hh:96
const SpaceVec _cell_extent
The extent of each cell of this square discretization (same for all)
Definition: square.hh:55
IndexType cell_at(const SpaceVec &pos) const override
Return the ID of the cell covering the given point in physical space.
Definition: square.hh:190
std::set< IndexType > boundary_cells(std::string select="all") const override
Retrieve a set of cell indices that are at a specified boundary.
Definition: square.hh:257
NBFuncID< Base > get_nb_func(NBMode nb_mode, const Config &nb_params) override
Retrieve the neighborhood function depending on the mode and parameters.
Definition: square.hh:403
SpaceVec barycenter_of(const IndexType id) const override
Returns the barycenter of the cell with the given ID.
Definition: square.hh:140
MultiIndex shape() const override
Get shape of the square grid.
Definition: square.hh:111
NBFuncID< Base > get_nb_func_vonNeumann(const Config &nb_params)
Returns a standalone von-Neumann neighborhood function.
Definition: square.hh:441
SquareGrid(std::shared_ptr< Space > space, const Config &cfg)
Construct a rectangular grid discretization.
Definition: square.hh:64
void add_neighbors_in_(const IndexType root_id, IndexContainer &neighbor_ids) const
Add both direct neighbors to a container of indices.
Definition: square.hh:920
NBFuncID< Base > get_nb_func_Moore(const Config &nb_params)
Returns a standalone Moore neighborhood function.
Definition: square.hh:713
DistType get_nb_param_distance(const Config &params) const
Extract the distance neighborhood parameter from the given config.
Definition: square.hh:1159
std::vector< SpaceVec > vertices_of(const IndexType id) const override
Returns the vertices of the cell with the given ID.
Definition: square.hh:160
NBFuncID< Base > _nb_Moore_nonperiodic
Moore neighbors for non-periodic 2D grid.
Definition: square.hh:844
NBFuncID< Base > _nb_Moore_periodic
Moore neighbors for periodic 2D grid.
Definition: square.hh:820
MultiIndex midx_of(const IndexType id) const override
Returns the multi-index of the cell with the given ID.
Definition: square.hh:125
SpaceVec extent_of(const IndexType) const override
Returns the extent of the cell with the given ID.
Definition: square.hh:149
SpaceVec effective_resolution() const override
The effective cell resolution into each physical space dimension.
Definition: square.hh:105
MultiIndexType< dim > MultiIndex
The type of multi-index like arrays, e.g. the grid shape.
Definition: square.hh:43
void add_high_val_neighbor_in_(const IndexType root_id, const DistType distance, IndexContainer &neighbor_ids) const
Add a neighbor on the high (ID) value side to an index container.
Definition: square.hh:1032
static constexpr DimType dim
The dimensionality of the space to be discretized (for easier access)
Definition: square.hh:37
typename Space::SpaceVec SpaceVec
The type of vectors that have a relation to physical space.
Definition: square.hh:40
MultiIndex determine_shape() const
Given the resolution, return the grid shape required to fill the space.
Definition: square.hh:388
NBFuncID< Base > _nb_vonNeumann_periodic
The Von-Neumann neighborhood for periodic grids.
Definition: square.hh:643
DistType expected_num_neighbors(const NBMode &nb_mode, const Config &nb_params) const override
Computes the expected number of neighbors for a neighborhood mode.
Definition: square.hh:1090
constexpr IndexType id_shift() const
Return the shift in cell indices necessary if moving along an axis.
Definition: square.hh:891
const MultiIndex _shape
The (multi-index) shape of the grid, resulting from resolution.
Definition: square.hh:52
NBFuncID< Base > _nb_vonNeumann_nonperiodic
The Von-Neumann neighborhood for non-periodic grids.
Definition: square.hh:670
void add_low_val_neighbor_in_(const IndexType root_id, const DistType distance, IndexContainer &neighbor_ids) const
Add a neighbor on the low (ID) value side to an index container.
Definition: square.hh:980
NBMode
Possible neighborhood types; availability depends on choice of grid.
Definition: base.hh:52
std::string nb_mode_to_string(const NBMode &nb_mode)
Given an NBMode enum value, return the corresponding string key.
Definition: base.hh:78
GridStructure
Available grid implementations.
Definition: base.hh:13
std::function< IndexContainer(const IndexType)> NBFuncID
Type of the neighborhood calculating function.
Definition: base.hh:92
@ vonNeumann
The vonNeumann neighborhood, i.e. only nearest neighbors.
@ Moore
The Moore neighborhood, i.e. nearest and next nearest neighbors.
@ empty
Every entity is utterly alone in the world.
@ square
A square lattice grid.
YAML::Node Config
Type of a variadic dictionary-like data structure used throughout Utopia.
Definition: types.hh:71
@ vertices
Iterate over vertices.
Definition: agent.hh:11
arma::Col< IndexType >::fixed< dim > MultiIndexType
Type for index type vectors that are associated with a physical space.
Definition: types.hh:53
std::vector< IndexType > IndexContainer
Type for container of indices.
Definition: types.hh:43
unsigned short DimType
Type for dimensions, i.e. very small unsigned integers.
Definition: types.hh:34
unsigned int DistType
Type for distancens, i.e. intermediately long unsigned integers.
Definition: types.hh:37
std::size_t IndexType
Type for indices, i.e. values used for container indexing, agent IDs, ...
Definition: types.hh:40
SpaceVecType< dim > SpaceVec
The type for vectors relating to physical space.
Definition: space.hh:33
static constexpr std::size_t dim
The dimensionality of the space.
Definition: space.hh:30