Utopia  2
Framework for studying models of complex & adaptive systems.
ContDisease.hh
Go to the documentation of this file.
1 #ifndef UTOPIA_MODELS_CONTDISEASE_HH
2 #define UTOPIA_MODELS_CONTDISEASE_HH
3 
4 #include <functional>
5 #include <random>
6 
7 // Utopia-related includes
8 #include <utopia/core/model.hh>
9 #include <utopia/core/apply.hh>
11 #include <utopia/core/select.hh>
12 
13 // ContDisease-realted includes
14 #include "params.hh"
15 #include "state.hh"
16 
17 
19 
20 // ++ Type definitions ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
21 
22 // Specialize the CellTraits type helper for this model
29 
30 
33 
34 
35 // ++ Model definition ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
36 
38 
51  public Model<ContDisease, CDTypes>
52 {
53 public:
56 
58  using DataGroup = typename Base::DataGroup;
59 
61  using DataSet = typename Base::DataSet;
62 
65 
67  using Cell = typename CellManager::Cell;
68 
71 
73  using RuleFunc = typename CellManager::RuleFunc;
74 
75 private:
76  // Base members: _time, _name, _cfg, _hdfgrp, _rng, _monitor, _space
77  // ... but you should definitely check out the documentation ;)
78 
79  // -- Members -------------------------------------------------------------
82 
84  const Params _params;
85 
87  std::uniform_real_distribution<double> _prob_distr;
88 
90  unsigned int _cluster_id_cnt;
91 
93  std::vector<std::shared_ptr<CellManager::Cell>> _cluster_members;
94 
96 
101  std::array<double, 5> _densities;
102 
103  // .. Data-Output related members .........................................
105 
107  std::shared_ptr<DataSet> _dset_densities;
108 
110  std::shared_ptr<DataSet> _dset_kind;
111 
113  std::shared_ptr<DataSet> _dset_age;
114 
116  std::shared_ptr<DataSet> _dset_cluster_id;
117 
118 
119 public:
121 
129  template<class ParentModel>
131  const std::string& name,
132  ParentModel& parent_model,
133  const DataIO::Config& custom_cfg = {}
134  )
135  :
136  // Initialize first via base model
137  Base(name, parent_model, custom_cfg),
138 
139  // Initialize the cell manager, binding it to this model
140  _cm(*this),
141 
142  // Carry over Parameters
143  _params(this->_cfg),
144 
145  // Initialize remaining members
146  _prob_distr(0., 1.),
147  _cluster_id_cnt(),
149  _densities{}, // undefined here, will be set in constructor body
150  _write_only_densities(get_as<bool>("write_only_densities",
151  this->_cfg)),
152 
153  // Create the dataset for the densities; shape is known
154  _dset_densities(this->create_dset("densities", {5})),
155 
156  // Create dataset for cell states
157  _dset_kind(this->create_cm_dset("kind", _cm)),
158 
159  // Create dataset for tree age
160  _dset_age(this->create_cm_dset("age", _cm)),
161 
162  // Create dataset for cluster id
163  _dset_cluster_id(this->create_cm_dset("cluster_id", _cm))
164  {
165  // Make sure the densities are not undefined
166  _densities.fill(std::numeric_limits<double>::quiet_NaN());
167 
168  // Cells are already set up by the CellManager.
169  // Remaining initialization steps regard only macroscopic quantities,
170  // e.g. the setup of heterogeneities: Stones and infection source.
171 
172  // Stones
173  if (_cfg["stones"] and get_as<bool>("enabled", _cfg["stones"])) {
174  this->_log->info("Setting cells to be stones ...");
175 
176  // Get the container
177  auto to_turn_to_stone = _cm.select_cells(_cfg["stones"]);
178 
179  // Apply a rule to all cells of that container: turn to stone
180  apply_rule<Update::async, Shuffle::off>(
181  [](const auto& cell){
182  auto& state = cell->state;
183  state.kind = Kind::stone;
184  return state;
185  },
186  to_turn_to_stone
187  );
188 
189  this->_log->info("Set {} cells to be stones using selection mode "
190  "'{}'.", to_turn_to_stone.size(),
191  get_as<std::string>("mode", _cfg["stones"]));
192  }
193 
194  // Ignite some cells permanently: fire sources
195  if ( _cfg["infection_source"]
196  and get_as<bool>("enabled", _cfg["infection_source"]))
197  {
198  this->_log->info("Setting cells to be infection sources ...");
199  auto source_cells = _cm.select_cells(_cfg["infection_source"]);
200 
201  apply_rule<Update::async, Shuffle::off>(
202  [](const auto& cell){
203  auto& state = cell->state;
204  state.kind = Kind::source;
205  return state;
206  },
207  source_cells
208  );
209 
210  this->_log->info("Set {} cells to be infection sources using "
211  "selection mode '{}'.", source_cells.size(),
212  get_as<std::string>("mode", _cfg["infection_source"]));
213  }
214 
215  // Add attributes to density dataset that provide coordinates
216  _dset_densities->add_attribute("dim_name__1", "kind");
217  _dset_densities->add_attribute("coords_mode__kind", "values");
218  _dset_densities->add_attribute("coords__kind",
219  std::vector<std::string>{
220  "empty", "tree", "infected", "source", "stone"
221  });
222  this->_log->debug("Added coordinates to densities dataset.");
223 
224  // Initialization should be finished here.
225  this->_log->debug("{} model fully set up.", this->_name);
226  }
227 
228 protected:
229  // .. Helper functions ....................................................
231 
239  // Temporarily overwrite every entry in the densities with zeroes
240  _densities.fill(0);
241 
242  // Count the occurrence of each possible state. Use the _densities
243  // member for that in order to not create a new array.
244  for (const auto& cell : this->_cm.cells()) {
245  // Cast enum to integer to arrive at the corresponding index
246  ++_densities[static_cast<char>(cell->state.kind)];
247  }
248  // The _densities array now contains the counts.
249 
250  // Calculate the actual densities by dividing the counts by the total
251  // number of cells.
252  for (auto&& d : _densities){
253  d /= static_cast<double>(this->_cm.cells().size());
254  }
255  };
256 
257 
259 
264  // reset cluster counter
265  _cluster_id_cnt = 0;
266  apply_rule<Update::async, Shuffle::off>(_identify_cluster,
267  _cm.cells());
268  }
269 
271 
282  // Check that time matches the first element of the sorted queue of
283  // time steps at which to apply the given number of infections.
284  if (not _params.infection_control.at_times.empty()){
285  // Check whether time has come for infections
286  if (this->_time == _params.infection_control.at_times.front()){
287  // Select cells that are trees
288  // (not empty, stones, infected, or source)
289  const auto cells_pool =
291  [&](const auto& cell){
292  return (cell->state.kind == Kind::tree);
293  }
294  );
295 
296  // Sample cells from the pool and ...
298  std::sample(
299  cells_pool.begin(), cells_pool.end(),
300  std::back_inserter(sample),
302  *this->_rng
303  );
304 
305  // ... and infect the sampled cells
306  for (const auto& cell : sample){
307  cell->state.kind = Kind::infected;
308  }
309 
310  // Done. Can now remove first element of the queue.
312  }
313  }
314 
315  // Change p_infect if the iteration step matches the ones
316  // specified in the configuration. This leads to constant time lookup.
317  if (not _params.infection_control.change_p_infect.empty()) {
318  const auto change_p_infect =
320 
321  if (this->_time == change_p_infect.first) {
322  _params.p_infect = change_p_infect.second;
323 
324  // Done. Can now remove the element from the queue.
326  }
327  }
328  }
329 
330  // .. Rule functions ......................................................
331 
333 
339  RuleFunc _update = [this](const auto& cell){
340  // Get the current state of the cell and reset its cluster ID
341  auto state = cell->state;
342  state.cluster_id = 0;
343 
344  // Distinguish by current state
345  if (state.kind == Kind::empty) {
346  // With a probability of p_growth, set the cell's state to tree
347  if (_prob_distr(*this->_rng) < _params.p_growth){
348  state.kind = Kind::tree;
349  return state;
350  }
351  }
352  else if (state.kind == Kind::tree){
353  // Increase the age of the tree
354  ++state.age;
355 
356  // Tree can be infected by neighbor or by random-point-infection.
357 
358  // Determine whether there will be a point infection
359  if (_prob_distr(*this->_rng) < _params.p_infect) {
360  // Yes, point infection occurred.
361  state.kind = Kind::infected;
362  return state;
363  }
364  else {
365  // Go through neighbor cells (according to Neighborhood type),
366  // and check if they are infected (or an infection source).
367  // If yes, infect cell with the probability 1-p_immunity.
368  for (const auto& nb: this->_cm.neighbors_of(cell)) {
369  // Get the neighbor cell's state
370  auto nb_state = nb->state;
371 
372  if ( nb_state.kind == Kind::infected
373  or nb_state.kind == Kind::source)
374  {
375  // With a certain probability, become infected
376  if (_prob_distr(*this->_rng) > _params.p_immunity) {
377  state.kind = Kind::infected;
378  return state;
379  }
380  }
381  }
382  }
383  }
384  else if (state.kind == Kind::infected) {
385  // Decease -> become an empty cell
386  state.kind = Kind::empty;
387 
388  // Reset the age of the cell to 0
389  state.age = 0;
390 
391  return state;
392  }
393  // else: other cell states need no update
394 
395  // Return the (potentially changed) cell state for the next round
396  return state;
397  };
398 
400  RuleFunc _identify_cluster = [this](const auto& cell){
401  if (cell->state.cluster_id != 0 or cell->state.kind != Kind::tree) {
402  // already labelled, nothing to do. Return current state
403  return cell->state;
404  }
405  // else: need to label this cell
406 
407  // Increment the cluster ID counter and label the given cell
408  _cluster_id_cnt++;
409  cell->state.cluster_id = _cluster_id_cnt;
410 
411  // Use existing cluster member container, clear it, add current cell
412  auto& cluster = _cluster_members;
413  cluster.clear();
414  cluster.push_back(cell);
415 
416  // Perform the percolation
417  for (unsigned int i = 0; i < cluster.size(); ++i) {
418  // Iterate over all potential cluster members c, i.e. all
419  // neighbors of cell cluster[i] that is already in the cluster
420  for (const auto& nb : this->_cm.neighbors_of(cluster[i])) {
421  // If it is a tree that is not yet in the cluster, add it.
422  if ( nb->state.cluster_id == 0
423  and nb->state.kind == Kind::tree)
424  {
425  nb->state.cluster_id = _cluster_id_cnt;
426  cluster.push_back(nb);
427  // This extends the outer for-loop...
428  }
429  }
430  }
431 
432  return cell->state;
433  };
434 
435 
436 public:
437  // -- Public Interface ----------------------------------------------------
438  // .. Simulation Control ..................................................
439 
441 
447  void perform_step () {
448  // Apply infection control if enabled
451  }
452 
453  // Apply the update rule to all cells.
454  apply_rule<Update::sync>(_update, _cm.cells());
455  // NOTE The cell state is updated synchronously, i.e.: only after all
456  // cells have been visited and know their state for the next step
457  }
458 
459 
461 
463  void monitor () {
465  this->_monitor.set_entry("densities", _densities);
466  }
467 
468 
470 
473  void write_data () {
474  // Update densities and write them
476  _dset_densities->write(_densities);
477  // NOTE Although stones and source density are not changing, they are
478  // calculated anyway and writing them again is not a big cost
479  // (two doubles) while making analysis much easier.
480 
481  // If only the densities are to be written, can stop here.
482  if (_write_only_densities) {
483  return;
484  }
485 
486  // Write the cell state
487  _dset_kind->write(_cm.cells().begin(), _cm.cells().end(),
488  [](const auto& cell) {
489  return static_cast<char>(cell->state.kind);
490  }
491  );
492 
493  // Write the tree ages
494  _dset_age->write(_cm.cells().begin(), _cm.cells().end(),
495  [](const auto& cell) {
496  return static_cast<unsigned short int>(cell->state.age);
497  }
498  );
499 
500  // Identify clusters and write them out
502  _dset_cluster_id->write(_cm.cells().begin(), _cm.cells().end(),
503  [](const auto& cell) {
504  return cell->state.cluster_id;
505  });
506  }
507 };
508 
509 
510 } // namespace Utopia::Models::ContDisease
511 
512 #endif // UTOPIA_MODELS_CONTDISEASE_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< CDCellTraits > 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
Time _time
Model-internal current time stamp.
Definition: model.hh:170
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
typename ModelTypes::DataGroup DataGroup
Data type that is used for storing datasets.
Definition: model.hh:122
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
Contagious disease model on a grid.
Definition: ContDisease.hh:52
std::array< double, 5 > _densities
Densities for all states.
Definition: ContDisease.hh:101
void update_densities()
Update the densities array.
Definition: ContDisease.hh:238
RuleFunc _update
Define the update rule.
Definition: ContDisease.hh:339
void identify_clusters()
Identify clusters.
Definition: ContDisease.hh:263
void write_data()
Write data.
Definition: ContDisease.hh:473
typename Base::DataGroup DataGroup
Data type for a data group.
Definition: ContDisease.hh:58
void perform_step()
Iterate a single time step.
Definition: ContDisease.hh:447
std::shared_ptr< DataSet > _dset_cluster_id
The dataset for storing the cluster ID associated with each cell.
Definition: ContDisease.hh:116
RuleFunc _identify_cluster
Identify each cluster of trees.
Definition: ContDisease.hh:400
void infection_control()
Apply infection control.
Definition: ContDisease.hh:281
typename CellManager::RuleFunc RuleFunc
Rule function type.
Definition: ContDisease.hh:73
Utopia::CellContainer< Cell > CellContainer
Type of a container of shared pointers to cells.
Definition: ContDisease.hh:70
Model< ContDisease, CDTypes > Base
The base model type.
Definition: ContDisease.hh:55
CellManager _cm
The cell manager.
Definition: ContDisease.hh:81
unsigned int _cluster_id_cnt
The incremental cluster tag.
Definition: ContDisease.hh:90
typename Base::DataSet DataSet
Data type for a dataset.
Definition: ContDisease.hh:61
std::shared_ptr< DataSet > _dset_densities
2D dataset (densities array and time) of density values
Definition: ContDisease.hh:107
void monitor()
Monitor model information.
Definition: ContDisease.hh:463
const Params _params
Model parameters.
Definition: ContDisease.hh:84
std::vector< std::shared_ptr< CellManager::Cell > > _cluster_members
A temporary container for use in cluster identification.
Definition: ContDisease.hh:93
typename CellManager::Cell Cell
Type of a cell.
Definition: ContDisease.hh:67
std::uniform_real_distribution< double > _prob_distr
The range [0, 1] distribution to use for probability checks.
Definition: ContDisease.hh:87
ContDisease(const std::string &name, ParentModel &parent_model, const DataIO::Config &custom_cfg={})
Construct the ContDisease model.
Definition: ContDisease.hh:130
std::shared_ptr< DataSet > _dset_kind
2D dataset (cell ID and time) of cell kinds
Definition: ContDisease.hh:110
bool _write_only_densities
Definition: ContDisease.hh:104
std::shared_ptr< DataSet > _dset_age
2D dataset (tree age and time) of cells
Definition: ContDisease.hh:113
YAML::Node Config
Type of a variadic dictionary-like data structure used throughout Utopia.
Definition: types.hh:71
@ condition
Select if a condition is fulfilled.
@ sample
Select a random sample of entities with a known sample size.
Definition: ContDisease.hh:18
@ stone
Cell cannot be infected.
@ source
Cell is an infection source: constantly infected, spreading infection.
@ tree
Cell represents a tree.
EntityContainer< CellType > CellContainer
Type of the variably sized container for cells.
Definition: types.hh:26
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
TimesValuesQueue change_p_infect
Change p_infect to new value at given times.
Definition: params.hh:37
const std::size_t num_additional_infections
The number of infections added to the default p_infect.
Definition: params.hh:27
TimesQueue at_times
Add additional infections at these time steps.
Definition: params.hh:30
const bool enabled
Whether infection control is enabled.
Definition: params.hh:24
Parameters of the ContDisease.
Definition: params.hh:133
const double p_growth
Probability per site and time step to go from state empty to tree.
Definition: params.hh:135
const double p_immunity
Definition: params.hh:139
double p_infect
Definition: params.hh:143
const InfectionContParams infection_control
Infection control parameters.
Definition: params.hh:146