Utopia  2
Framework for studying models of complex & adaptive systems.
GameOfLife.hh
Go to the documentation of this file.
1 #ifndef UTOPIA_MODELS_GAMEOFLIFE_HH
2 #define UTOPIA_MODELS_GAMEOFLIFE_HH
3 
4 // standard library includes
5 #include <random>
6 #include <string>
7 #include <algorithm>
8 
9 // third-party library includes
10 
11 // Utopia-related includes
12 #include <utopia/core/model.hh>
14 #include <utopia/core/apply.hh>
15 #include <utopia/core/select.hh>
17 
19 {
20 // ++ Type definitions ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
21 
23 struct CellState
24 {
26  bool living;
27 
29 
36  CellState() : living(false) {};
37 };
38 
40 
47 
50 
51 // ++ Model definition ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
52 
54 
56 class GameOfLife : public Model<GameOfLife, ModelTypes>
57 {
58  public:
61 
63  using DataGroup = typename Base::DataGroup;
64 
66  using DataSet = typename Base::DataSet;
67 
70  // NOTE that it requires the model's type as second template argument
71 
73  using NbLifeRule = std::unordered_set<unsigned short>;
74 
76 
83  using RuleFunc = typename CellManager::RuleFunc;
84 
85  private:
86  // Base members: _time, _name, _cfg, _hdfgrp, _rng, _monitor, _log, _space
87  // ... but you should definitely check out the documentation ;)
88 
89  // -- Members -------------------------------------------------------------
92 
94  const std::string _rule;
95 
98 
101 
102  // .. Temporary objects ...................................................
103 
104  // .. Datasets ............................................................
106  std::shared_ptr<DataSet> _dset_living;
107 
108  public:
109  // -- Model Setup ---------------------------------------------------------
111 
114  template<class ParentModel>
115  GameOfLife(const std::string name, ParentModel& parent) :
116  // Initialize first via base model
117  Base(name, parent),
118 
119  // Now initialize the cell manager
120  _cm(*this),
121 
122  // Initialize the rule and extract the number of neighbors required
123  // for birth and survival
124  _rule(get_as<std::string>("rule", this->_cfg)),
127 
128  // Datasets
129  // For setting up datasets that store CellManager data, you can use the
130  // helper functions to take care of setting them up:
131  _dset_living(this->create_cm_dset("living", _cm))
132  {
133  // Select the cells that should be set to living
134  const auto living = this->_cm.select_cells(
135  get_as<DataIO::Config>("living", this->_cfg));
136  for (const auto& living_cell : living) {
137  living_cell->state.living = true;
138  }
139 
140  // Initialization should be finished here.
141  this->_log->debug("{} model fully set up.", this->_name);
142  }
143 
144  private:
145  // .. Setup functions .....................................................
148  {
149  const std::string delimiter = "/";
150  const std::string birth_s = _rule.substr(0, _rule.find(delimiter));
151 
152  NbLifeRule birth;
153  for (auto b_char : birth_s) {
154  // Subtracting '0' is needed to get from the ASCII number stored as
155  // char to the corresponding unsigned short value.
156  auto b = static_cast<unsigned short>(b_char - '0');
157  birth.emplace(b);
158  }
159  return birth;
160  }
161 
164  {
165  // Get the substring specifying `survive` which is the substring that
166  // starts after the delimiter and ends with the end of the rule string.
167  const std::string delimiter = "/";
168  const auto pos_delimiter = _rule.find(delimiter);
169  const std::string survive_s =
170  _rule.substr(pos_delimiter + 1, _rule.size() - pos_delimiter);
171 
172  NbLifeRule survive;
173  for (auto b_char : survive_s) {
174  // Subtracting '0' is needed to get from the ASCII number stored as
175  // char to the corresponding unsigned short value.
176  auto b = static_cast<unsigned short>(b_char - '0');
177  survive.emplace(b);
178  }
179  return survive;
180  }
181 
182  // .. Helper functions ....................................................
183 
186  {
187  double sum = 0.;
188  for (const auto& cell : _cm.cells()) {
189  sum += cell->state.living;
190  }
191  return sum / _cm.cells().size();
192  }
193 
194  // .. Rule functions ......................................................
196  const RuleFunc _life_rule = [this](const auto& cell) {
197  // Get the current state of the cell
198  auto state = cell->state;
199 
200  // Calculate the number of living neighbors
201  auto num_living_nbs {0u};
202  for (auto nb : this->_cm.neighbors_of(cell)) {
203  if (nb->state.living) {
204  ++num_living_nbs;
205  }
206  }
207 
208  const bool survive =
209  std::find(_survive.begin(), _survive.end(), num_living_nbs) !=
210  _survive.end();
211  // Die if the number of living neighbors is not in the survival
212  // container
213  if (not survive) {
214  state.living = false;
215  }
216 
217  // Give birth if the number of living neighbors is in the birth
218  // container
219  const bool birth =
220  std::find(_birth.begin(), _birth.end(), num_living_nbs) !=
221  _birth.end();
222  if (birth) {
223  state.living = true;
224  }
225 
226  // Return the new cell state
227  return state;
228  };
229 
230  public:
231  // -- Public Interface ----------------------------------------------------
232  // .. Simulation Control ..................................................
233 
236  {
237  // Apply the rules to all cells, first the interaction, then the update
238  apply_rule<Update::sync>(_life_rule, _cm.cells());
239  }
240 
242 
250  void monitor()
251  {
252  this->_monitor.set_entry("living_cell_density",
254  }
255 
257 
261  void write_data()
262  {
263  // Write out the some_state of all cells
264  _dset_living->write(_cm.cells().begin(),
265  _cm.cells().end(),
266  [](const auto& cell) {
267  return static_cast<char>(cell->state.living);
268  });
269  }
270 
271  // .. Getters and setters .................................................
272  // Add getters and setters here to interface with other models
273 };
274 
275 } // namespace Utopia::Models::GameOfLife
276 
277 #endif // UTOPIA_MODELS_GAMEOFLIFE_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
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
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
The GameOfLife Model.
Definition: GameOfLife.hh:57
const NbLifeRule _birth
The number of neighbors required to get born.
Definition: GameOfLife.hh:97
const NbLifeRule _survive
The number of neighbors required to survive.
Definition: GameOfLife.hh:100
std::shared_ptr< DataSet > _dset_living
A dataset for storing all cells living or dead status.
Definition: GameOfLife.hh:106
const std::string _rule
The rule in Mirek's Cellebration notation.
Definition: GameOfLife.hh:94
double calculate_living_cell_density() const
Calculate the mean of all cells' some_state.
Definition: GameOfLife.hh:185
typename Base::DataGroup DataGroup
Data type of the group to write model data to, holding datasets.
Definition: GameOfLife.hh:63
typename Base::DataSet DataSet
Data type for a dataset.
Definition: GameOfLife.hh:66
const RuleFunc _life_rule
Implement the general life-like rule.
Definition: GameOfLife.hh:196
NbLifeRule extract_birth_from_rule()
Extract the number of neighbors required for birth from the rule.
Definition: GameOfLife.hh:147
CellManager _cm
The cell manager.
Definition: GameOfLife.hh:91
NbLifeRule extract_survive_from_rule()
Extract the number of neighbors required to survive from the rule.
Definition: GameOfLife.hh:163
typename CellManager::RuleFunc RuleFunc
Extract the type of the rule function from the CellManager.
Definition: GameOfLife.hh:83
void monitor()
Monitor model information.
Definition: GameOfLife.hh:250
std::unordered_set< unsigned short > NbLifeRule
Type of container to store the number of neighbors for the life rule.
Definition: GameOfLife.hh:73
void perform_step()
Iterate a single step.
Definition: GameOfLife.hh:235
void write_data()
Write data.
Definition: GameOfLife.hh:261
GameOfLife(const std::string name, ParentModel &parent)
Construct the GameOfLife model.
Definition: GameOfLife.hh:115
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
Definition: GameOfLife.hh:19
Definition: parallel.hh:235
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
The type of a cell's state.
Definition: GameOfLife.hh:24
CellState()
Construct the cell state with all dead cells as default.
Definition: GameOfLife.hh:36
bool living
Whether a cell lives or not.
Definition: GameOfLife.hh:26