Utopia  2
Framework for studying models of complex & adaptive systems.
SandPile.hh
Go to the documentation of this file.
1 #ifndef UTOPIA_MODELS_SANDPILE_HH
2 #define UTOPIA_MODELS_SANDPILE_HH
3 
4 #include <numeric>
5 #include <functional>
6 #include <queue>
7 
8 #include <utopia/core/model.hh>
9 #include <utopia/core/apply.hh>
11 
12 
13 namespace Utopia {
14 namespace Models {
15 namespace SandPile {
16 
17 // ++ Type definitions ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
18 
20 using Slope = unsigned int;
21 
23 struct State {
26 
29 
31  State() = delete;
32 
34  template<typename RNG>
35  State(const DataIO::Config& cfg, const std::shared_ptr<RNG>& rng)
36  :
37  in_avalanche{false}
38  {
39  // Read in the initial slope range
40  const auto initial_slope_lower_limit =
41  get_as<Slope>("initial_slope_lower_limit", cfg);
42 
43  const auto initial_slope_upper_limit =
44  get_as<Slope>("initial_slope_upper_limit", cfg);
45 
46  // Make sure the parameters are valid
47  if (initial_slope_upper_limit <= initial_slope_lower_limit) {
48  throw std::invalid_argument("The `inital_slope_*_limit` "
49  "parameters need to specify a valid range, i.e. with `lower` "
50  "being strictly smaller than the `upper`!");
51  }
52 
53  // Depending on initial slope, set the initial slope of all cells to
54  // a random value in that interval
55  std::uniform_int_distribution<Slope>
56  dist(initial_slope_lower_limit, initial_slope_upper_limit);
57 
58  // Set the initial slopes that are not relaxed, yet.
59  slope = dist(*rng);
60  }
61 };
62 
64 
69 
70 
73 
74 
75 // ++ Model definition ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
76 
78 
84 class SandPile:
85  public Model<SandPile, SandPileTypes>
86 {
87 public:
90 
93 
95  using Cell = typename CellManager::Cell;
96 
98  using CellContainer = std::vector<std::shared_ptr<Cell>>;
99 
101  using RuleFunc = typename CellManager::RuleFunc;
102 
104  using DataSet = typename Base::DataSet;
105 
107  using UniformIntDist = typename std::uniform_int_distribution<std::size_t>;
108 
109 
110 private:
111  // Base members: _time, _name, _cfg, _hdfgrp, _rng, _monitor, _space
112  // ... but you should definitely check out the documentation ;)
113 
114  // -- Members -------------------------------------------------------------
117 
118  // -- Model parameters -- //
121 
123  const unsigned int _topple_num_grains;
124 
125 
126  // .. Writing-related parameters ..........................................
129 
130 
131  // .. Temporary objects ...................................................
134 
135 
136  // .. Datasets ............................................................
138  const std::shared_ptr<DataSet> _dset_slope;
139 
141  const std::shared_ptr<DataSet> _dset_avalanche;
142 
144  const std::shared_ptr<DataSet> _dset_avalanche_size;
145 
146 
147 public:
148  // -- Model Setup ---------------------------------------------------------
150 
158  template<class ParentModel>
160  const std::string& name,
161  ParentModel& parent_model,
162  const DataIO::Config& custom_cfg = {}
163  )
164  :
165  // Initialize first via base model
166  Base(name, parent_model, custom_cfg),
167 
168  // Initialize the cell manager, binding it to this model
169  _cm(*this),
170 
171  // Initialize other class members
172  _critical_slope(get_as<Slope>("critical_slope", _cfg)),
174 
175  // Writing-related parameters
177  get_as<bool>("write_only_avalanche_size", _cfg, false)
178  ),
179 
180  // Initialize the distribution such that a random cell can be selected
181  _cell_distr(0, _cm.cells().size() - 1),
182 
183  // create datasets
184  _dset_slope(this->create_cm_dset("slope", _cm)),
185  _dset_avalanche(this->create_cm_dset("avalanche", _cm)),
186  _dset_avalanche_size(this->create_dset("avalanche_size", {}))
187  {
188  // Check neighborhood mode; currently does not work with Moore
189  if (_cm.nb_mode() != NBMode::vonNeumann) {
190  throw std::invalid_argument(
191  "Other neighborhoods than vonNeumann are not supported!"
192  );
193  }
194 
195  // Add a dimension label for the avalanche size dataset and store the
196  // size of the grid as attribute, allowing to compute the avalanche
197  // size area fraction without the need for spatial data
198  _dset_avalanche_size->add_attribute("dim_names", "time");
199  _dset_avalanche_size->add_attribute("num_cells", _cm.cells().size());
200 
201  // Perform initial step
202  this->_log->info("Adding first grain of sand and letting topple ...");
203  this->_log->debug("Toppling size: {}", _topple_num_grains);
204 
205  if (_cm.cells().size() > 4000) {
206  this->_log->info("With {} cells, this may take a while ...",
207  _cm.cells().size());
208  }
210 
211  // Done.
212  this->_log->info("{} all set up.", this->_name);
213  }
214 
215 
216 private:
217  // .. Helper functions ....................................................
218 
220 
222  unsigned int avalanche_size() const {
223  return
224  std::accumulate(_cm.cells().begin(), _cm.cells().end(),
225  0,
226  [](unsigned int sum, const auto& cell){
227  if (cell->state.in_avalanche) {
228  return sum + 1;
229  }
230  return sum;
231  }
232  );
233  }
234 
235  // .. Dynamic functions ...................................................
237  const std::shared_ptr<Cell>& add_sand_grain() {
238  // Select a random cell to be modified
239  auto& cell = _cm.cells()[_cell_distr(*this->_rng)];
240 
241  // Adjust that cell's state: add a grain of sand
242  this->_log->trace("Adding grain of sand to cell {} ...", cell->id());
243  cell->state.slope += 1;
244 
245  // As the slope of this grain changed, it is regarded as "in avalanche"
246  // NOTE This does NOT mean that it is supercritical and that it will
247  // lead to toppling in the topple method.
248  cell->state.in_avalanche = true;
249 
250  // Return the cell reference such that the topple method can use that
251  // information to do its thing
252  return cell;
253  };
254 
256 
263  void topple(const std::shared_ptr<Cell>& first_cell) {
264  this->_log->trace("Now toppling all supercritical cells ...");
265 
266  // Create a queue that stores all the cells that need to topple
267  std::queue<std::shared_ptr<Cell>> queue;
268  queue.push(first_cell);
269 
270  while (not queue.empty()) {
271  // Get the first element from the queue, extract its state and
272  // remove it from the queue.
273  const auto& cell = queue.front();
274  auto& state = cell->state;
275  queue.pop();
276 
277  // A cell will topple only if its slope is greater than the
278  // critical slope.
279  if (state.slope > _critical_slope) {
280  state.slope -= _topple_num_grains;
281  state.in_avalanche = true;
282 
283  // Add grains (=slopes) to the neighbors
284  // and add only neighbors with supercritical slope to the queue
285  for (const auto& nb : _cm.neighbors_of(cell)){
286  auto& nb_slope = nb->state.slope;
287  nb_slope += 1;
288  if (nb_slope > _critical_slope){
289  queue.push(nb);
290  }
291  }
292  }
293  }
294  }
295 
296  // .. Rule functions ......................................................
297  // Define functions that can be applied to the cells of the grid
298 
300 
303  RuleFunc _reset = [](const auto& cell){
304  cell->state.in_avalanche = false;
305 
306  return cell->state;
307  };
308 
309 
310 public:
311  // -- Public Interface ----------------------------------------------------
312  // .. Simulation Control ..................................................
314  void perform_step () {
315  // Reset cells: All cells are not touched by an avalanche
316  apply_rule<Update::async, Shuffle::off>(_reset, _cm.cells());
317 
318  // Add a grain of sand and, starting from the cell the grain fell on,
319  // let all supercritical cells topple until a relaxed state is reached
320  topple(add_sand_grain());
321  }
322 
323 
325 
327  void monitor () {
328  // Supply the last avalanche size to the monitor
329  this->_monitor.set_entry("avalanche_size", avalanche_size());
330  // NOTE As the monitor is called very infrequently, it is not a large
331  // overhead to re-calculate the avalanche size here; cheaper and
332  // simpler than storing it and implementing logic of whether to
333  // re-calculate it or not.
334  }
335 
336 
338  void write_data () {
339  // Calculate and write the avalanche size; may stop after that
340  _dset_avalanche_size->write(avalanche_size());
341 
342  if (_write_only_avalanche_size) {
343  return;
344  }
345 
346  // Write the slope of all cells
347  _dset_slope->write(_cm.cells().begin(), _cm.cells().end(),
348  [](const auto& cell) {
349  return cell->state.slope;
350  }
351  );
352 
353  // Write a mask of whether a cell was touched by an avalanche. Most
354  // feasible data type for that is char, which is the only C-native
355  // 8-bit data type and thus the only type supported by HDF5
356  _dset_avalanche->write(_cm.cells().begin(), _cm.cells().end(),
357  [](const auto& cell) {
358  return static_cast<char>(cell->state.in_avalanche);
359  }
360  );
361  }
362 };
363 
364 } // namespace SandPile
365 } // namespace Models
366 } // namespace Utopia
367 
368 #endif // UTOPIA_MODELS_SANDPILE_HH
const CellContainer< Cell > & cells() const
Return const reference to the managed CA cells.
Definition: cell_manager.hh:219
const NBMode & nb_mode() const
Return the currently selected neighborhood mode.
Definition: cell_manager.hh:443
typename std::function< CellState(const std::shared_ptr< Cell > &)> RuleFunc
The type of a rule function acting on cells of this cell manager.
Definition: cell_manager.hh:84
auto nb_size() const
Return the (maximum) size of the currently selected neighborhood.
Definition: cell_manager.hh:450
CellContainer< Cell > neighbors_of(const Cell &cell) const
Retrieve the given cell's neighbors.
Definition: cell_manager.hh:458
Utopia::Cell< CellTraits > Cell
Type of the managed cells.
Definition: cell_manager.hh:47
Base class interface for Models using the CRT Pattern.
Definition: model.hh:112
std::shared_ptr< DataSet > create_cm_dset(const std::string name, const CellManager &cm, const std::size_t compression_level=1, const std::vector< hsize_t > chunksize={})
Create a dataset storing data from a CellManager.
Definition: model.hh:849
typename ModelTypes::DataSet DataSet
Data type that is used for storing data.
Definition: model.hh:125
const Config _cfg
Config node belonging to this model instance.
Definition: model.hh:158
const std::string _name
Name of the model instance.
Definition: model.hh:149
std::shared_ptr< DataSet > create_dset(const std::string name, const std::shared_ptr< DataGroup > &hdfgrp, std::vector< hsize_t > add_write_shape, const std::size_t compression_level=1, const std::vector< hsize_t > chunksize={})
Create a new dataset within the given group.
Definition: model.hh:752
const std::shared_ptr< spdlog::logger > _log
The (model) logger.
Definition: model.hh:164
The SandPile Model.
Definition: SandPile.hh:86
const std::shared_ptr< DataSet > _dset_avalanche_size
Dataset to store the avalanche size for each time step.
Definition: SandPile.hh:144
void write_data()
Write the cell slope and avalanche flag to the datasets.
Definition: SandPile.hh:338
void topple(const std::shared_ptr< Cell > &first_cell)
Topple cells if the critical slope is exceeded.
Definition: SandPile.hh:263
const unsigned int _topple_num_grains
The number of grains that topple; depends on the neighborhood size.
Definition: SandPile.hh:123
std::vector< std::shared_ptr< Cell > > CellContainer
Cell container type.
Definition: SandPile.hh:98
const std::shared_ptr< DataSet > _dset_avalanche
Dataset to store the avalanche state of all cells for all time steps.
Definition: SandPile.hh:141
typename Base::DataSet DataSet
Data type for a dataset.
Definition: SandPile.hh:104
CellManager _cm
The grid manager.
Definition: SandPile.hh:116
typename std::uniform_int_distribution< std::size_t > UniformIntDist
The uniform integer distribution type to use.
Definition: SandPile.hh:107
const std::shared_ptr< Cell > & add_sand_grain()
Select a random cell, add a grain of sand to it, and return it.
Definition: SandPile.hh:237
typename CellManager::RuleFunc RuleFunc
Supply a type for rule functions that are applied to cells.
Definition: SandPile.hh:101
typename CellManager::Cell Cell
Cell type.
Definition: SandPile.hh:95
unsigned int avalanche_size() const
Calculate the avalanche size.
Definition: SandPile.hh:222
SandPile(const std::string &name, ParentModel &parent_model, const DataIO::Config &custom_cfg={})
Construct the SandPile model.
Definition: SandPile.hh:159
const Slope _critical_slope
The critical slope of the cells.
Definition: SandPile.hh:120
Model< SandPile, SandPileTypes > Base
The base model's type.
Definition: SandPile.hh:89
const bool _write_only_avalanche_size
If true, will only store the avalanche size, not the spatial data.
Definition: SandPile.hh:128
UniformIntDist _cell_distr
A distribution to select a random cell.
Definition: SandPile.hh:133
void perform_step()
Perform an iteration step.
Definition: SandPile.hh:314
const std::shared_ptr< DataSet > _dset_slope
Dataset to store the slopes of all cells for all time steps.
Definition: SandPile.hh:138
void monitor()
Supply monitor information to the frontend.
Definition: SandPile.hh:327
@ vonNeumann
The vonNeumann neighborhood, i.e. only nearest neighbors.
YAML::Node Config
Type of a variadic dictionary-like data structure used throughout Utopia.
Definition: types.hh:71
std::mt19937 rng
– Type definitions ----------------------------------------------------—
Definition: test_revision.cc:17
unsigned int Slope
Type of the slope.
Definition: SandPile.hh:20
Definition: agent.hh:11
The entity traits struct gathers types to be used for specializing an entity.
Definition: entity.hh:49
Wrapper struct for defining model class data types.
Definition: model.hh:92
Cell State for the SandPile model.
Definition: SandPile.hh:23
Slope slope
The current value of the slope.
Definition: SandPile.hh:25
bool in_avalanche
Whether the cell was touched by an avalanche; useful for updating.
Definition: SandPile.hh:28
State()=delete
Default constructor.
State(const DataIO::Config &cfg, const std::shared_ptr< RNG > &rng)
Configuration based constructor.
Definition: SandPile.hh:35