Utopia 2
Framework for studying models of complex & adaptive systems.
Loading...
Searching...
No Matches
SimpleEG.hh
Go to the documentation of this file.
1#ifndef UTOPIA_MODELS_SIMPLEEG_HH
2#define UTOPIA_MODELS_SIMPLEEG_HH
3
4#include <functional>
5
10
11
12namespace Utopia {
13namespace Models {
14namespace SimpleEG {
15
16// ++ Type definitions ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
17
19enum Strategy : unsigned short int { S0=0, S1=1 };
20
22struct CellState {
25
27 double payoff;
28
31 :
32 strategy(S0),
33 payoff(0.)
34 {}
35};
36
37
39
48
49
50
53
54
55
56// ++ Model definition ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
57
59
71 public Model<SimpleEG, ModelTypes>
72{
73public:
76
78 using Config = typename Base::Config;
79
81 using DataSet = typename Base::DataSet;
82
85
87
96
98 using RNG = typename Base::RNG;
99
101 using IAMatrixType = typename std::array<std::array<double, 2>, 2>;
102
103
104private:
105 // Base members: _time, _name, _cfg, _hdfgrp, _rng, _log, _monitor, _space
106 // ... but you should definitely check out the documentation ;)
107
108 // -- Members -------------------------------------------------------------
111
114
115
116 // .. Temporary objects ...................................................
119
121 std::uniform_real_distribution<double> _prob_distr;
122
123
124 // .. Datasets ............................................................
126 std::shared_ptr<DataSet> _dset_strategy;
127
129 std::shared_ptr<DataSet> _dset_payoff;
130
131
132public:
133 // -- Public interface ----------------------------------------------------
135
143 template<class ParentModel>
145 const std::string& name,
147 const DataIO::Config& custom_cfg = {}
148 )
149 :
150 // Initialize first via base model
152
153 // Now initialize the cell manager
154 _cm(*this),
155
156 // Initialize model parameters
158
159 // ... and helpers objects
161 _prob_distr(0., 1.),
162
163 // And open datasets for strategy and payoff
164 _dset_strategy(this->create_cm_dset("strategy", _cm)),
166 {
167 // Initialize cells
168 this->initialize_cells();
169
170 // Write calculated _ia_matrix to _hdfgrp attribute
171 this->_hdfgrp->add_attribute("ia_matrix", _ia_matrix);
172 }
173
174
175private:
176 // Setup functions ........................................................
178
184 static_assert(Base::Space::dim == 2, "Only 2D space is supported.");
185
186 // Extract the mode that determines the initial strategy
187 auto initial_state = get_as<std::string>("initial_state", this->_cfg);
188
189 this->_log->info("Initializing cells in '{}' mode ...", initial_state);
190
191 // Distinguish according to the mode, which strategy to choose
192 if (initial_state == "random") {
193 // Get the threshold probability value
194 const auto s1_prob = get_as<double>("s1_prob", this->_cfg);
195
196 // Define the update rule
197 auto set_random_strategy = [this, &s1_prob](const auto cell) {
198 auto state = cell->state();
199
200 // Draw a random number and compare it to the threshold
201 if (this->_prob_distr(*this->_rng) < s1_prob) {
202 state.strategy = Strategy::S1;
203 }
204 else {
205 state.strategy = Strategy::S0;
206 }
207
208 return state;
209 };
210
211 // Apply the rule to all cells
213 }
214
215 else if (initial_state == "fraction") {
216 // Get the value for the fraction of cells to have strategy 1
217 const auto s1_fraction = get_as<double>("s1_fraction", this->_cfg);
218
219 if (s1_fraction > 1. or s1_fraction < 0.) {
220 throw std::invalid_argument("Need `s1_fraction` in [0, 1], "
221 "but got value: " + std::to_string(s1_fraction));
222 }
223
224 // Calculate the number of cells that should have that strategy
225 const auto num_cells = _cm.cells().size();
226 const std::size_t num_s1 = s1_fraction * num_cells;
227 // NOTE this is a flooring calculation!
228
229 this->_log->debug("Cells with strategy 1: {} of {}",
230 num_s1, num_cells);
231
232 // TODO Optionally, can add some logic here to make more clever
233 // assignments, i.e. starting out with all S1 if the number
234 // to set is higher than half ...
235
236 // Need a counter of cells that were set to S1
237 std::size_t num_set = 0;
238
239 // Get a copy of the cells container... and shuffle them.
240 auto random_cells = _cm.cells();
241 std::shuffle(random_cells.begin(), random_cells.end(),
242 *this->_rng);
243
244 // Make num_s1 cells use strategy 1
245 for (const auto& cell : random_cells) {
246 // If the desired number of cells using strategy 1 is not yet reached change another cell's strategy
247 if (num_set < num_s1) {
248 // Check if it already has strategy 1.
249 if (cell->state().strategy == Strategy::S1) {
250 // Already has strategy 1, don't set it again
251 continue;
252 }
253 // else: has strategy 0 -> set to S1 and increment counter
254 cell->state_new().strategy = Strategy::S1;
255 cell->update();
256
257 num_set++;
258 }
259 // Break, if fraction of strategy 1 is reached
260 else break;
261 }
262 }
263
264 else if (initial_state == "single_s0" or initial_state == "single_s1"){
265 // Get the grid shape and perform checks on it
266 const auto& grid_shape = _cm.grid()->shape();
267
268 // Need to throw an error for non-odd-valued grid shape
269 if (grid_shape[0] % 2 == 0 or grid_shape[1] % 2 == 0) {
270 throw std::invalid_argument("In mode '" + initial_state + "', "
271 "odd grid extensions are required to calculate the "
272 "central cell! Either adapt your grid resolution or the "
273 "space's extent in order to have a different shape. "
274 );
275 }
276 // FIXME This and the below is rather fragile and should be
277 // improved. Better approach: calculate the central point
278 // (here!) and find the cell beneath that point, setting it
279 // to single_strategy. Then use rule application to set
280 // default_strategy.
281
282 // Determine which strategy is the common default strategy
283 // and which one is the single strategy in the center of the grid
285 if (initial_state == "single_s0") {
288 }
289 else {
292 }
293
294 // Define the rule function
295 auto set_initial_strategy = [&](const auto& cell) {
296 // Get the state of this cell
297 auto state = cell->state();
298
299 // Get the multi index to find out if this cell is central
300 const auto midx = _cm.midx_of(cell);
301
302 if ( midx[0] == grid_shape[0] / 2
303 and midx[1] == grid_shape[1] / 2) {
304 // The cell _is_ in the center of the grid
305 state.strategy = single_strategy;
306 }
307 else {
308 // The cell _is not_ in the center of the grid
309 state.strategy = default_strategy;
310 }
311
312 return state;
313 };
314
315 // Apply the rule
317 }
318 else {
319 throw std::invalid_argument("Invalid value '" + initial_state
320 + "' for parameter `initial_state`! Allowed values: random, "
321 "fraction, single_s0, single_s1.");
322 }
323
324 // Done.
325 this->_log->info("Initialized all {} cells.", _cm.cells().size());
326 }
327
328 // .. Helper functions ....................................................
329
331
357 // Return the ia_matrix if it is explicitly given in the config
358 if (this->_cfg["ia_matrix"]) {
359 return get_as<IAMatrixType>("ia_matrix", this->_cfg);
360 }
361 else if (this->_cfg["bc_pair"]) {
362 // If ia_matrix is not provided in the config, get the ia_matrix
363 // from the bc-pair
364 const auto [b, c] = get_as<std::pair<double, double>>("bc_pair",
365 this->_cfg);
366 const double ia_00 = b - c;
367 const double ia_01 = -c;
368 const double ia_10 = b;
369 const double ia_11 = 0.;
370
371 const std::array<double,2> row0({{ia_00, ia_01}});
372 const std::array<double,2> row1({{ia_10, ia_11}});
373
374 return IAMatrixType({{row0, row1}});
375 }
376 else if (this->_cfg["b"]) {
377 // If both previous cases are not provided, then return the
378 // ia_matrix given by the parameter "b"
379 const auto b = get_as<double>("b", this->_cfg);
380
381 if (b <= 1.) {
382 throw std::invalid_argument("Parameter `b` needs to be >1, "
383 "but was: " + std::to_string(b));
384 }
385
386 const double ia_00 = 1;
387 const double ia_01 = 0;
388 const double ia_10 = b;
389 const double ia_11 = 0.;
390
391 const std::array<double, 2> row0({{ia_00, ia_01}});
392 const std::array<double, 2> row1({{ia_10, ia_11}});
393
394 return IAMatrixType({{row0, row1}});;
395 }
396
397 // If we reach this point, not enough parameters were provided
398 throw std::invalid_argument("No interaction matrix given! Check that "
399 "at least one of the following config "
400 "entries is available: `ia_matrix`, "
401 "`bc_pair`, `b`");
402 }
403
404
405 // .. Rule Functions ......................................................
406 // Rule functions that can be applied to the CellManager's cells
407
409
412 RuleFunc _interaction = [this](const auto& cell){
413 // Get the state of the current cell and its strategy
414 auto state = cell->state();
415
416 // First, reset payoff to zero
417 state.payoff = 0.;
418
419 // Go through neighboring cells, look at their strategies and add
420 // the corresponding payoff only to the current cell's payoff.
421 // NOTE: Careful! Adding the corresponding payoff to the neighboring
422 // cell would lead to payoffs being added multiple times!
423 for (const auto& nb : this->_cm.neighbors_of(cell)) {
424 const auto nb_strategy = nb->state().strategy;
425
426 // Calculate the payoff depending on this cell's and the other
427 // cell's state
428 if (state.strategy == S0 and nb_strategy == S0) {
429 state.payoff += _ia_matrix[0][0];
430 }
431 else if (state.strategy == S0 and nb_strategy == S1) {
432 state.payoff += _ia_matrix[0][1];
433 }
434 else if (state.strategy == S1 and nb_strategy == S0) {
435 state.payoff += _ia_matrix[1][0];
436 }
437 else if (state.strategy == S1 and nb_strategy == S1) {
438 state.payoff += _ia_matrix[1][1];
439 }
440 }
441
442 // Return the updated state of this cell
443 return state;
444 };
445
447
458 RuleFunc _update = [this](const auto& cell){
459 // Get the state of the cell
460 auto state = cell->state();
461
462 // Set highest payoff in the neighborhood to the cell's payoff
463 double highest_payoff = state.payoff;
466
467 // Iterate over neighbors of this cell:
468 for (const auto& nb : this->_cm.neighbors_of(cell)){
469 if (nb->state().payoff > highest_payoff) {
470 // Found a new highest payoff
471 highest_payoff = nb->state().payoff;
473 _fittest_cells_in_nbhood.push_back(nb);
474 }
475 else if (nb->state().payoff == highest_payoff) {
476 // Have a payoff equal to that of another cell
477 _fittest_cells_in_nbhood.push_back(nb);
478 }
479 // else: payoff was below highest payoff
480 }
481
482 // Now, update the strategy according to the fittest neighbor
483 if (_fittest_cells_in_nbhood.size() == 1) {
484 // Only one fittest neighbor. -> The state of the current cell
485 // is updated with that of the fittest neighbor.
486 state.strategy = _fittest_cells_in_nbhood[0]->state().strategy;
487 }
488 else if (_fittest_cells_in_nbhood.size() > 1) {
489 // There are multiple nbs with the same (highest) payoff.
490 // -> Choose randomly one of them to pass on its strategy
491 std::uniform_int_distribution<> dist(0, _fittest_cells_in_nbhood.size()-1);
492 state.strategy = _fittest_cells_in_nbhood[dist(*this->_rng)]->state().strategy;
493 }
494 else {
495 // There is no fittest neighbor. This case should never occur!
496 throw std::runtime_error("There was no fittest neighbor in the "
497 "cell update. Should not have occurred!");
498 }
499
500 return state;
501 };
502
503
504public:
505 // -- Public Interface ----------------------------------------------------
506 // .. Simulation Control ..................................................
507
518 void perform_step () {
519 // Apply the rules to all cells
522 }
523
524
526 void monitor () {
527
528 }
529
530
532 void write_data () {
533 // strategy
534 _dset_strategy->write(_cm.cells().begin(), _cm.cells().end(),
535 [](const auto& cell) {
536 return static_cast<unsigned short int>(cell->state().strategy);
537 }
538 );
539
540 // payoffs
541 _dset_payoff->write(_cm.cells().begin(), _cm.cells().end(),
542 [](const auto& cell) {
543 return cell->state().payoff;
544 }
545 );
546 }
547
548 // .. Getters and setters .................................................
549 // Add getters and setters here to interface with other models
550
551};
552
553
554} // namespace SimpleEG
555} // namespace Models
556} // namespace Utopia
557#endif // UTOPIA_MODELS_SIMPLEEG_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
MultiIndex midx_of(const Cell &cell) const
Returns the multi-index of the given cell.
Definition cell_manager.hh:236
CellContainer< Cell > neighbors_of(const Cell &cell) const
Retrieve the given cell's neighbors.
Definition cell_manager.hh:458
const std::shared_ptr< GridType > & grid() const
Return const reference to the grid.
Definition cell_manager.hh:214
const CellContainer< Cell > & cells() const
Return const reference to the managed CA cells.
Definition cell_manager.hh:219
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
const std::shared_ptr< DataGroup > _hdfgrp
The HDF group this model instance should write its data to.
Definition model.hh:176
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
typename ModelTypes::Config Config
Data type that holds the configuration.
Definition model.hh:116
typename ModelTypes::RNG RNG
Data type of the shared RNG.
Definition model.hh:128
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
Simple model of evolutionary games on grids.
Definition SimpleEG.hh:72
typename CellManager::RuleFunc RuleFunc
Extract the type of the rule function from the CellManager.
Definition SimpleEG.hh:95
typename Base::RNG RNG
Data type of the shared RNG.
Definition SimpleEG.hh:98
typename Base::Config Config
Data type that holds the configuration.
Definition SimpleEG.hh:78
RuleFunc _update
The update rule.
Definition SimpleEG.hh:458
RuleFunc _interaction
The interaction between players.
Definition SimpleEG.hh:412
CellManager _cm
The cell manager.
Definition SimpleEG.hh:110
void perform_step()
Iterate a single step.
Definition SimpleEG.hh:518
std::shared_ptr< DataSet > _dset_payoff
Stores cell payoffs over time.
Definition SimpleEG.hh:129
std::shared_ptr< DataSet > _dset_strategy
Stores cell strategies over time.
Definition SimpleEG.hh:126
SimpleEG(const std::string &name, ParentModel &parent_model, const DataIO::Config &custom_cfg={})
Construct the SimpleEG model.
Definition SimpleEG.hh:144
IAMatrixType extract_ia_matrix() const
Extract the interaction matrix from the config file.
Definition SimpleEG.hh:356
typename std::array< std::array< double, 2 >, 2 > IAMatrixType
Type of the interaction matrix.
Definition SimpleEG.hh:101
void monitor()
Monitor model information.
Definition SimpleEG.hh:526
void write_data()
Write data: the strategy and payoff of each cell.
Definition SimpleEG.hh:532
void initialize_cells()
Initialize the cells according to initial_state config parameter.
Definition SimpleEG.hh:183
Model< SimpleEG, ModelTypes > Base
The base model.
Definition SimpleEG.hh:75
typename Base::DataSet DataSet
Data type for a dataset.
Definition SimpleEG.hh:81
std::uniform_real_distribution< double > _prob_distr
Uniform real distribution in [0., 1.) used for evaluating probabilities.
Definition SimpleEG.hh:121
const IAMatrixType _ia_matrix
The interaction matrix (extracted during initialization)
Definition SimpleEG.hh:113
CellContainer< typename CellManager::Cell > _fittest_cells_in_nbhood
A container to temporarily accumulate the fittest neighbor cells in.
Definition SimpleEG.hh:118
YAML::Node Config
Type of a variadic dictionary-like data structure used throughout Utopia.
Definition types.hh:71
Container select_entities(const Manager &mngr, const DataIO::Config &sel_cfg)
Select entities according to parameters specified in a configuration.
Definition select.hh:213
void apply_rule(Rule &&rule, const ContTarget &cont_target, ContArgs &&... cont_args)
Sequential overload.
Definition apply.hh:133
Strategy
Strategy enum.
Definition SimpleEG.hh:19
@ S1
Definition SimpleEG.hh:19
@ S0
Definition SimpleEG.hh:19
Definition agent.hh:11
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
The type of the cell state for the SimpleEG model.
Definition SimpleEG.hh:22
CellState()
Default constructor: strategy S0 and zero payoff.
Definition SimpleEG.hh:30
double payoff
The payoff.
Definition SimpleEG.hh:27
Strategy strategy
The strategy.
Definition SimpleEG.hh:24