Utopia  2
Framework for studying models of complex & adaptive systems.
ForestFire.hh
Go to the documentation of this file.
1 #ifndef UTOPIA_MODELS_FORESTFIRE_HH
2 #define UTOPIA_MODELS_FORESTFIRE_HH
3 
4 #include <functional>
5 #include <random>
6 #include <numeric>
7 
8 #include <utopia/core/model.hh>
9 #include <utopia/core/apply.hh>
11 
12 #include "../ContDisease/state.hh"
13 
14 namespace Utopia {
15 namespace Models {
16 namespace ForestFire {
17 
18 // ++ Type definitions ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
19 
21 
25 
26 
28 struct State {
31 
33  unsigned short age;
34 
36  unsigned int cluster_id;
37 
39  State () = delete;
40 
42  template<class RNG>
43  State (const DataIO::Config& cfg, const std::shared_ptr<RNG>& rng)
44  :
45  kind(Kind::empty),
46  age(0),
47  cluster_id(0)
48  {
49  // Get the desired probability to be a tree
50  const auto p_tree = get_as<double>("p_tree", cfg);
51 
52  // Check obvious cases (no need to draw a random number)
53  if (p_tree < 0. or p_tree > 1.) {
54  throw std::invalid_argument("p_tree needs to be in interval "
55  "[0., 1.], but was not!");
56  }
57  else if (p_tree == 0.) {
58  return;
59  }
60  else if (p_tree == 1.) {
61  kind = Kind::tree;
62  return;
63  }
64 
65  // With this probability, the cell state is a tree
66  if (std::uniform_real_distribution<double>(0., 1.)(*rng) < p_tree) {
67  kind = Kind::tree;
68  }
69  // NOTE Although the distribution object is created each time, this
70  // is not a significant slow-down compared to re-using an
71  // existing distribution object (<4%). When compiled with
72  // optimization flags, that slowdown is even smaller...
73  }
74 };
75 
76 
78 
86 
87 
89 struct Param {
91  const double p_growth;
92 
94  const double p_lightning;
95 
97  const double p_immunity;
98 
100  Param(const DataIO::Config& cfg)
101  :
102  p_growth(get_as<double>("p_growth", cfg)),
103  p_lightning(get_as<double>("p_lightning", cfg)),
104  p_immunity(get_as<double>("p_immunity", cfg))
105  {
106  if ((p_growth > 1.) or (p_growth < 0.)) {
107  throw std::invalid_argument("Invalid p_growth! Need be a value "
108  "in range [0, 1] and specify the probability per time step "
109  "and cell with which an empty cell turns into a tree.");
110  }
111  if ((p_lightning > 1.) or (p_lightning < 0.)) {
112  throw std::invalid_argument("Invalid p_lightning! Need be "
113  "in range [0, 1] and specify the probability per cell and "
114  "time step for lightning to strike.");
115  }
116  if ((p_immunity > 1.) or (p_immunity < 0.)) {
117  throw std::invalid_argument("Invalid p_immunity! "
118  "Need be a value in range [0, 1] and specify the probability "
119  "per neighbor with which that neighbor is immune to fire.");
120  }
121  }
122 };
123 
124 
127 
128 
129 // ++ Model definition ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
130 
132 
137  public Model<ForestFire, ModelTypes>
138 {
139 public:
142 
144  using DataSet = typename Base::DataSet;
145 
148 
150  using Cell = typename CellManager::Cell;
151 
153  using RuleFunc = typename CellManager::RuleFunc;
154 
155 
156 private:
157  // Base members: _time, _name, _cfg, _hdfgrp, _rng, _monitor, _log, _space
158  // ... but you should definitely check out the documentation ;)
159 
160  // -- Members -------------------------------------------------------------
163 
165  const Param _param;
166 
168  std::uniform_real_distribution<double> _prob_distr;
169 
171  unsigned int _cluster_id_cnt;
172 
174  std::vector<std::shared_ptr<CellManager::Cell>> _cluster_members;
175 
176  // .. Output-related ......................................................
179 
181  const std::shared_ptr<DataSet> _dset_kind;
182 
184  const std::shared_ptr<DataSet> _dset_age;
185 
187  const std::shared_ptr<DataSet> _dset_cluster_id;
188 
190  const std::shared_ptr<DataSet> _dset_tree_density;
191 
192 
193 public:
194  // -- Model Setup ---------------------------------------------------------
196 
204  template<class ParentModel>
206  const std::string& name,
207  ParentModel& parent_model,
208  const DataIO::Config& custom_cfg = {}
209  )
210  :
211  // Initialize first via base model
212  Base(name, parent_model, custom_cfg),
213 
214  // Initialize the cell manager, binding it to this model
215  _cm(*this),
216 
217  // Carry over parameters
218  _param(this->_cfg),
219 
220  // Initialize remaining members
221  _prob_distr(0., 1.),
222  _cluster_id_cnt(0),
224  _write_only_tree_density(get_as<bool>("write_only_tree_density",
225  this->_cfg)),
226 
227  // Create datasets using the helper functions for CellManager-data
228  _dset_kind{this->create_cm_dset("kind", _cm)},
229  _dset_age{this->create_cm_dset("age", _cm)},
230  _dset_cluster_id{this->create_cm_dset("cluster_id", _cm)},
231  _dset_tree_density{this->create_dset("tree_density", {})}
232  {
233  // Cells are already set up in the CellManager.
234  // Take care of the heterogeneities now:
235 
236  // Stones
237  if (_cfg["stones"] and get_as<bool>("enabled", _cfg["stones"])) {
238  this->_log->info("Setting cells to be stones ...");
239 
240  // Get the container
241  auto to_turn_to_stone = _cm.select_cells(_cfg["stones"]);
242 
243  // Apply a rule to all cells of that container: turn to stone
244  apply_rule<Update::async, Shuffle::off>(
245  [](const auto& cell){
246  auto& state = cell->state;
247  state.kind = Kind::stone;
248  return state;
249  },
250  to_turn_to_stone
251  );
252 
253  this->_log->info("Set {} cells to be stones using selection mode "
254  "'{}'.", to_turn_to_stone.size(),
255  get_as<std::string>("mode", _cfg["stones"]));
256  }
257 
258  // Ignite some cells permanently: fire sources
259  if ( _cfg["ignite_permanently"]
260  and get_as<bool>("enabled", _cfg["ignite_permanently"]))
261  {
262  this->_log->info("Setting cells to be permanently ignited ...");
263  auto to_be_ignited = _cm.select_cells(_cfg["ignite_permanently"]);
264 
265  apply_rule<Update::async, Shuffle::off>(
266  [](const auto& cell){
267  auto& state = cell->state;
268  state.kind = Kind::source;
269  return state;
270  },
271  to_be_ignited
272  );
273 
274  this->_log->info("Set {} cells to be permanently ignited using "
275  "selection mode '{}'.", to_be_ignited.size(),
276  get_as<std::string>("mode", _cfg["ignite_permanently"]));
277  }
278 
279  this->_log->debug("{} model fully set up.", this->_name);
280  }
281 
282 
283 private:
284  // .. Setup functions .....................................................
285 
286  // .. Helper functions ....................................................
288  double calculate_tree_density() const {
289  // NOTE If execution policies are implemented, this could be easily
290  // made parallel by replacing std::accumulate with std::reduce
291  // and adding std::execution::par from the header
292  // <execution> as first argument.
293  return
294  std::accumulate(_cm.cells().begin(), _cm.cells().end(),
295  0,
296  [&](std::size_t sum, const std::shared_ptr<Cell>& cell) {
297  return sum + (cell->state.kind == Kind::tree);
298  }
299  )
300  / static_cast<double>(_cm.cells().size());
301  }
302 
304 
309  unsigned int identify_clusters() {
310  this->_log->debug("Identifying clusters...");
311 
312  // Reset counter for cluster IDs, then call the identification function
313  _cluster_id_cnt = 0;
314 
315  apply_rule<Update::async, Shuffle::off>(
317  );
318 
319  this->_log->debug("Identified {} clusters.", _cluster_id_cnt);
320 
321  return _cluster_id_cnt;
322  }
323 
324  // .. Rule functions ......................................................
326 
341  RuleFunc _update = [this](const auto& cell){
342  // Get the current state of the cell and reset the cluster tag
343  auto& state = cell->state;
344  state.cluster_id = 0;
345 
346  // Empty cells can grow a tree
347  if (state.kind == Kind::empty) {
348  if (this->_prob_distr(*this->_rng) < _param.p_growth) {
349  state.kind = Kind::tree;
350  }
351  }
352 
353  // Trees can be hit by lightning or continue living
354  else if (state.kind == Kind::tree) {
355  // Can be hit by lightning
356  if (this->_prob_distr(*this->_rng) < _param.p_lightning) {
357  state = _burn_cluster(cell);
358  }
359  else {
360  // Lives. Increase its age
361  state.age++;
362  }
363  }
364 
365  // Permanently ignited cells always burn the cluster
366  else if (state.kind == Kind::source) {
367  state = _burn_cluster(cell);
368  }
369 
370  // Stones don't do anything
371  else if (state.kind == Kind::stone) {
372  // Not doing anything, like the good stone I am ...
373  }
374 
375  else {
376  // Should never occur!
377  throw std::invalid_argument("Invalid cell state!");
378  }
379 
380  return state;
381  };
382 
384 
387  RuleFunc _burn_cluster = [this](const auto& cell) {
388  // A tree cell should burn, i.e.: transition to empty.
389  if (cell->state.kind == Kind::tree) {
390  cell->state.kind = Kind::empty;
391  }
392  // The only other possibility would be a fire source: remains alight!
393 
394  // Use existing cluster member container, clear it, add current cell
395  auto& cluster = _cluster_members;
396  cluster.clear();
397  cluster.push_back(cell);
398 
399  // Recursively go over all cluster members
400  for (unsigned int i = 0; i < cluster.size(); ++i) {
401  const auto& cluster_member = cluster[i];
402 
403  // Iterate over all potential cluster members
404  for (const auto& c : this->_cm.neighbors_of(cluster_member)) {
405  // If it is a tree, it will burn ...
406  if (c->state.kind == Kind::tree) {
407  // ... unless there is p_immunity > 0 ...
408  if (this->_param.p_immunity > 0.) {
409  // ... where there is a chance not to burn:
410  if ( this->_prob_distr(*this->_rng)
411  < _param.p_immunity) {
412  continue;
413  }
414  }
415 
416  // Bad luck. Burn.
417  c->state.kind = Kind::empty;
418  c->state.age = 0;
419  cluster.push_back(c);
420  // This extends the outer for-loop
421  }
422  }
423  }
424 
425  // Return the current cell's adjusted state.
426  return cell->state;
427  };
428 
430  /* Runs a percolation on a cell, that has ID 0. Then, give all cells of
431  * that percolation the same ID. The ``_cluster_id_cnt`` member keeps
432  * track of already given IDs.
433  * Both a cell's cluster ID and the cluster ID counter are reset as part of
434  * a regular iteration step.
435  */
436  RuleFunc _identify_cluster = [this](const auto& cell){
437  // Only need to continue if a tree and not already labelled
438  if ( cell->state.cluster_id != 0
439  or cell->state.kind != Kind::tree)
440  {
441  return cell->state;
442  }
443  // else: is an unlabelled tree; need to label it.
444 
445  // Increment the cluster ID counter and label the given cell
446  _cluster_id_cnt++;
447  cell->state.cluster_id = _cluster_id_cnt;
448 
449  // Use existing cluster member container, clear it, add current cell
450  auto& cluster = _cluster_members;
451  cluster.clear();
452  cluster.push_back(cell);
453 
454  // Perform the percolation
455  for (unsigned int i = 0; i < cluster.size(); ++i) {
456  // Iterate over all potential cluster members c, i.e. all
457  // neighbors of cell cluster[i] that is already in the cluster
458  for (const auto& c : this->_cm.neighbors_of(cluster[i])) {
459  // If it is a tree that is not yet in the cluster, add it.
460  if ( c->state.cluster_id == 0
461  and c->state.kind == Kind::tree)
462  {
463  c->state.cluster_id = _cluster_id_cnt;
464  cluster.push_back(c);
465  // This extends the outer for-loop...
466  }
467  }
468  }
469 
470  return cell->state;
471  };
472 
473 
474 public:
475  // -- Public Interface ----------------------------------------------------
476  // .. Simulation Control ..................................................
477 
479  void perform_step () {
480  // Apply update rule on all cells, asynchronously and shuffled
481  apply_rule<Update::async, Shuffle::on>(
482  _update, _cm.cells(), *this->_rng
483  );
484  }
485 
487  void monitor () {
488  this->_monitor.set_entry("tree_density", calculate_tree_density());
489  }
490 
492  void write_data () {
493  // Calculate and write the tree density
494  _dset_tree_density->write(calculate_tree_density());
495 
497  // Done here.
498  return;
499  }
500 
501  // Store all cells' kind
502  _dset_kind->write(_cm.cells().begin(), _cm.cells().end(),
503  [](const auto& cell) {
504  return static_cast<char>(cell->state.kind);
505  });
506 
507  // ... and age
508  _dset_age->write(_cm.cells().begin(), _cm.cells().end(),
509  [](const auto& cell) {
510  return cell->state.age;
511  });
512 
513  // Identify the clusters (only needed when actually writing)
515  _dset_cluster_id->write(_cm.cells().begin(), _cm.cells().end(),
516  [](const auto& cell) {
517  return cell->state.cluster_id;
518  });
519  }
520 };
521 
522 } // namespace ForestFire
523 } // namespace Models
524 } // namespace Utopia
525 
526 #endif // UTOPIA_MODELS_FORESTFIRE_HH
const CellContainer< Cell > & cells() const
Return const reference to the managed CA cells.
Definition: cell_manager.hh:219
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 > select_cells(Args &&... args) const
Select cells using the Utopia::select_entities interface.
Definition: cell_manager.hh:342
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
Monitor _monitor
The monitor.
Definition: model.hh:188
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
const std::shared_ptr< RNG > _rng
The RNG shared between models.
Definition: model.hh:161
The ForestFire model.
Definition: ForestFire.hh:138
void perform_step()
Perform step.
Definition: ForestFire.hh:479
typename Base::DataSet DataSet
Data type for a dataset.
Definition: ForestFire.hh:144
CellManager _cm
The cell manager for the forest fire model.
Definition: ForestFire.hh:162
unsigned int identify_clusters()
Identifies clusters in the cells and labels them with corresponding IDs.
Definition: ForestFire.hh:309
void monitor()
Provide monitoring information to the frontend: tree_density
Definition: ForestFire.hh:487
Model< ForestFire, ModelTypes > Base
The base model type.
Definition: ForestFire.hh:141
RuleFunc _identify_cluster
Get the identity of each cluster of trees.
Definition: ForestFire.hh:436
double calculate_tree_density() const
Calculate and return the density of tree cells.
Definition: ForestFire.hh:288
RuleFunc _update
Update rule, called every step.
Definition: ForestFire.hh:341
std::uniform_real_distribution< double > _prob_distr
A [0,1]-range uniform distribution used for evaluating probabilities.
Definition: ForestFire.hh:168
const std::shared_ptr< DataSet > _dset_age
2D dataset (tree age and time) of cells
Definition: ForestFire.hh:184
unsigned int _cluster_id_cnt
The incremental cluster tag caching variable.
Definition: ForestFire.hh:171
std::vector< std::shared_ptr< CellManager::Cell > > _cluster_members
A temporary container for use in cluster identification.
Definition: ForestFire.hh:174
RuleFunc _burn_cluster
Rule to burn a cluster of trees around the given cell.
Definition: ForestFire.hh:387
typename CellManager::Cell Cell
The type of a cell.
Definition: ForestFire.hh:150
const std::shared_ptr< DataSet > _dset_tree_density
The dataset that stores the mean density.
Definition: ForestFire.hh:190
const std::shared_ptr< DataSet > _dset_kind
The dataset that stores the kind for each cell, e.g. Kind::tree.
Definition: ForestFire.hh:181
void write_data()
Write data.
Definition: ForestFire.hh:492
const bool _write_only_tree_density
Whether to only write the tree density.
Definition: ForestFire.hh:178
ForestFire(const std::string &name, ParentModel &parent_model, const DataIO::Config &custom_cfg={})
Construct the ForestFire model.
Definition: ForestFire.hh:205
typename CellManager::RuleFunc RuleFunc
Rule function type, extracted from CellManager.
Definition: ForestFire.hh:153
const Param _param
Model parameters.
Definition: ForestFire.hh:165
const std::shared_ptr< DataSet > _dset_cluster_id
The dataset that stores the cluster id.
Definition: ForestFire.hh:187
@ empty
Every entity is utterly alone in the world.
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
Kind
The kind of the cell: empty, tree, infected, source, stone.
Definition: state.hh:9
std::mt19937 rng
– Type definitions ----------------------------------------------------—
Definition: test_revision.cc:17
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
ForestFire model parameter struct.
Definition: ForestFire.hh:89
const double p_lightning
Frequency of lightning occurring per cell.
Definition: ForestFire.hh:94
Param(const DataIO::Config &cfg)
Construct the parameters from the given configuration node.
Definition: ForestFire.hh:100
const double p_growth
Rate of growth per cell.
Definition: ForestFire.hh:91
const double p_immunity
The probability (per neighbor) to be immune to a spreading fire.
Definition: ForestFire.hh:97
The full cell struct for the ForestFire model.
Definition: ForestFire.hh:28
State(const DataIO::Config &cfg, const std::shared_ptr< RNG > &rng)
Construct a cell from a configuration node and an RNG.
Definition: ForestFire.hh:43
unsigned int cluster_id
An ID denoting to which cluster this cell belongs (if it is a tree)
Definition: ForestFire.hh:36
unsigned short age
The age of the tree on this cell.
Definition: ForestFire.hh:33
Kind kind
The kind of object that populates this cell, e.g. a tree.
Definition: ForestFire.hh:30
State()=delete
Remove default constructor, for safety.