Utopia 2
Framework for studying models of complex & adaptive systems.
Loading...
Searching...
No Matches
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
11
12
13namespace Utopia {
14namespace Models {
15namespace SandPile {
16
17// ++ Type definitions ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
18
20using Slope = unsigned int;
21
23struct State {
26
29
31 State() = delete;
32
34 template<typename RNG>
35 State(const DataIO::Config& cfg, const std::shared_ptr<RNG>& rng)
36 :
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
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>
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
85 public Model<SandPile, SandPileTypes>
86{
87public:
90
93
95 using Cell = typename CellManager::Cell;
96
98 using CellContainer = std::vector<std::shared_ptr<Cell>>;
99
102
104 using DataSet = typename Base::DataSet;
105
107 using UniformIntDist = typename std::uniform_int_distribution<std::size_t>;
108
109
110private:
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
147public:
148 // -- Model Setup ---------------------------------------------------------
150
158 template<class ParentModel>
160 const std::string& name,
162 const DataIO::Config& custom_cfg = {}
163 )
164 :
165 // Initialize first via base model
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)),
173 _topple_num_grains(_cm.nb_size()),
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
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
216private:
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
310public:
311 // -- Public Interface ----------------------------------------------------
312 // .. Simulation Control ..................................................
314 void perform_step () {
315 // Reset cells: All cells are not touched by an avalanche
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
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
CellContainer< Cell > neighbors_of(const Cell &cell) const
Retrieve the given cell's neighbors.
Definition cell_manager.hh:458
const CellContainer< Cell > & cells() const
Return const reference to the managed CA cells.
Definition cell_manager.hh:219
Utopia::Cell< CellTraits > Cell
Type of the managed cells.
Definition cell_manager.hh:47
const NBMode & nb_mode() const
Return the currently selected neighborhood mode.
Definition cell_manager.hh:443
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
const std::shared_ptr< spdlog::logger > _log
The (model) logger.
Definition model.hh:164
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
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
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
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
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
ReturnType get_as(const std::string &key, const DataIO::Config &node)
This function is a wrapper around the yaml-cpp YAML::Node::as function.
Definition cfg_utils.hh:158
Container select_entities(const Manager &mngr, const DataIO::Config &sel_cfg)
Select entities according to parameters specified in a configuration.
Definition select.hh:213
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