pops-core  0.9
PoPS (Pest or Pathogen Spread) Model Core C++ library
simulation.hpp
Go to the documentation of this file.
1 /*
2  * PoPS model - pest or pathogen spread simulation
3  *
4  * Copyright (C) 2015-2020 by the authors.
5  *
6  * Authors: Zexi Chen (zchen22 ncsu edu)
7  * Vaclav Petras (wenzeslaus gmail com)
8  * Anna Petrasova (kratochanna gmail com)
9  * Chris Jones (cjones1688 gmail com)
10  *
11  * The code contained herein is licensed under the GNU General Public
12  * License. You may obtain a copy of the GNU General Public License
13  * Version 2 or later at the following locations:
14  *
15  * http://www.opensource.org/licenses/gpl-license.html
16  * http://www.gnu.org/copyleft/gpl.html
17  */
18 
19 #ifndef POPS_SIMULATION_HPP
20 #define POPS_SIMULATION_HPP
21 
22 #include <cmath>
23 #include <tuple>
24 #include <vector>
25 #include <random>
26 #include <string>
27 #include <stdexcept>
28 
29 #include "utils.hpp"
30 
31 namespace pops {
32 
39 template<typename Container>
40 void rotate_left_by_one(Container& container)
41 {
42  std::rotate(container.begin(), container.begin() + 1, container.end());
43 }
44 
47 template<typename Generator>
48 std::vector<int> draw_n_from_v(std::vector<int> v, unsigned n, Generator& generator)
49 {
50  if (n > v.size())
51  n = v.size();
52 
53  std::shuffle(v.begin(), v.end(), generator);
54  v.erase(v.begin() + n, v.end());
55  return v;
56 }
57 
60 enum class ModelType
61 {
64 };
65 
71 inline ModelType model_type_from_string(const std::string& text)
72 {
73  if (text == "SI" || text == "SusceptibleInfected" || text == "susceptible-infected"
74  || text == "susceptible_infected")
76  else if (
77  text == "SEI" || text == "SusceptibleExposedInfected"
78  || text == "susceptible-exposed-infected"
79  || text == "susceptible_exposed_infected")
81  else
82  throw std::invalid_argument(
83  "model_type_from_string: Invalid"
84  " value '"
85  + text + "' provided");
86 }
87 
92 inline ModelType model_type_from_string(const char* text)
93 {
94  // call the string version
95  return model_type_from_string(text ? std::string(text) : std::string());
96 }
97 
125 template<typename IntegerRaster, typename FloatRaster, typename RasterIndex = int>
127 {
128 private:
129  RasterIndex rows_;
130  RasterIndex cols_;
131  bool dispersers_stochasticity_;
132  bool establishment_stochasticity_;
133  bool movement_stochasticity_;
134  ModelType model_type_;
135  unsigned latency_period_;
136  std::default_random_engine generator_;
137 
138 public:
158  unsigned random_seed,
159  RasterIndex rows,
160  RasterIndex cols,
162  unsigned latency_period = 0,
163  bool dispersers_stochasticity = true,
164  bool establishment_stochasticity = true,
165  bool movement_stochasticity = true)
166  : rows_(rows),
167  cols_(cols),
168  dispersers_stochasticity_(dispersers_stochasticity),
169  establishment_stochasticity_(establishment_stochasticity),
170  movement_stochasticity_(movement_stochasticity),
171  model_type_(model_type),
172  latency_period_(latency_period)
173  {
174  generator_.seed(random_seed);
175  }
176 
177  Simulation() = delete;
178 
187  void remove(
188  IntegerRaster& infected,
189  IntegerRaster& susceptible,
190  const FloatRaster& temperature,
191  double lethal_temperature,
192  const std::vector<std::vector<int>>& suitable_cells)
193  {
194  for (auto indices : suitable_cells) {
195  int i = indices[0];
196  int j = indices[1];
197  if (temperature(i, j) < lethal_temperature) {
198  // move infested/infected host back to susceptible pool
199  susceptible(i, j) += infected(i, j);
200  // remove all infestation/infection in the infected class
201  infected(i, j) = 0;
202  }
203  }
204  }
205 
223  void mortality(
224  IntegerRaster& infected,
225  IntegerRaster& total_hosts,
226  double mortality_rate,
227  int mortality_time_lag,
228  IntegerRaster& died,
229  std::vector<IntegerRaster>& mortality_tracker_vector,
230  const std::vector<std::vector<int>>& suitable_cells)
231  {
232 
233  int max_index = mortality_tracker_vector.size() - mortality_time_lag - 1;
234 
235  for (auto indices : suitable_cells) {
236  int i = indices[0];
237  int j = indices[1];
238  for (int index = 0; index <= max_index; index++) {
239  int mortality_in_index = 0;
240  if (mortality_tracker_vector[index](i, j) > 0) {
241  // used to ensure that all infected hosts in the last year of
242  // tracking mortality
243  if (index == 0) {
244  mortality_in_index = mortality_tracker_vector[index](i, j);
245  }
246  else {
247  mortality_in_index =
248  mortality_rate * mortality_tracker_vector[index](i, j);
249  }
250  mortality_tracker_vector[index](i, j) -= mortality_in_index;
251  died(i, j) += mortality_in_index;
252  if (infected(i, j) > 0) {
253  infected(i, j) -= mortality_in_index;
254  }
255  if (total_hosts(i, j) > 0) {
256  total_hosts(i, j) -= mortality_in_index;
257  }
258  }
259  }
260  }
261 
262  rotate_left_by_one(mortality_tracker_vector);
263  }
264 
286  unsigned movement(
287  IntegerRaster& infected,
288  IntegerRaster& susceptible,
289  std::vector<IntegerRaster>& mortality_tracker_vector,
290  std::vector<IntegerRaster>& exposed,
291  IntegerRaster& resistant,
292  IntegerRaster& total_hosts,
293  IntegerRaster& total_exposed,
294  unsigned step,
295  unsigned last_index,
296  const std::vector<std::vector<int>>& movements,
297  std::vector<unsigned> movement_schedule,
298  std::vector<std::vector<int>>& suitable_cells)
299  {
300  for (unsigned i = last_index; i < movements.size(); i++) {
301  auto moved = movements[i];
302  unsigned move_schedule = movement_schedule[i];
303  if (move_schedule != step) {
304  return i;
305  }
306  int infected_moved = 0;
307  int exposed_moved = 0;
308  int susceptible_moved = 0;
309  int resistant_moved = 0;
310  int total_hosts_moved = 0;
311  int row_from = moved[0];
312  int col_from = moved[1];
313  int row_to = moved[2];
314  int col_to = moved[3];
315  int hosts = moved[4];
316  std::vector<int> exposed_categories;
317  std::vector<int> mortality_categories;
318  if (hosts > total_hosts(row_from, col_from)) {
319  total_hosts_moved = total_hosts(row_from, col_from);
320  }
321  else {
322  total_hosts_moved = hosts;
323  }
324  auto total_infecteds = infected(row_from, col_from);
325  auto suscepts = susceptible(row_from, col_from);
326  auto expose = total_exposed(row_from, col_from);
327  auto resist = resistant(row_from, col_from);
328  // set up vector of numeric categories (infected = 1, susceptible = 2,
329  // exposed = 3) for drawing # moved in each category
330  std::vector<int> categories(total_infecteds, 1);
331  categories.insert(categories.end(), suscepts, 2);
332  categories.insert(categories.end(), expose, 3);
333  categories.insert(categories.end(), resist, 4);
334 
335  std::vector<int> draw =
336  draw_n_from_v(categories, total_hosts_moved, generator_);
337  infected_moved = std::count(draw.begin(), draw.end(), 1);
338  susceptible_moved = std::count(draw.begin(), draw.end(), 2);
339  exposed_moved = std::count(draw.begin(), draw.end(), 3);
340  resistant_moved = std::count(draw.begin(), draw.end(), 4);
341 
342  if (exposed_moved > 0) {
343  int index = 0;
344  for (auto& raster : exposed) {
345  auto exposed_count = raster(row_from, col_from);
346  exposed_categories.insert(
347  exposed_categories.end(), exposed_count, index);
348 
349  index += 1;
350  }
351  std::vector<int> exposed_draw =
352  draw_n_from_v(exposed_categories, exposed_moved, generator_);
353  index = 0;
354  for (auto& raster : exposed) {
355  auto exposed_moved_in_cohort =
356  std::count(exposed_draw.begin(), exposed_draw.end(), index);
357  raster(row_from, col_from) -= exposed_moved_in_cohort;
358  raster(row_to, col_to) += exposed_moved_in_cohort;
359  index += 1;
360  }
361  }
362 
363  if (infected_moved > 0) {
364  std::vector<int> mortality_categories;
365  int index = 0;
366  for (auto& raster : mortality_tracker_vector) {
367  auto mortality_count = raster(row_from, col_from);
368  mortality_categories.insert(
369  mortality_categories.end(), mortality_count, index);
370  index += 1;
371  }
372  std::vector<int> mortality_draw =
373  draw_n_from_v(mortality_categories, infected_moved, generator_);
374  index = 0;
375  for (auto& raster : mortality_tracker_vector) {
376  auto mortality_moved_in_cohort =
377  std::count(mortality_draw.begin(), mortality_draw.end(), index);
378  raster(row_from, col_from) -= mortality_moved_in_cohort;
379  raster(row_to, col_to) += mortality_moved_in_cohort;
380  index += 1;
381  }
382  }
383  // check that the location with host movement is in suitable cells.
384  // Since suitable-cells comes from the total hosts originally. The
385  // the first check is for total_hosts
386  if (total_hosts(row_to, col_to) == 0) {
387  for (auto indices : suitable_cells) {
388  int i = indices[0];
389  int j = indices[1];
390  if ((i == row_to) && (j == col_to)) {
391  std::vector<int> added_index = {row_to, col_to};
392  suitable_cells.push_back(added_index);
393  break;
394  }
395  }
396  }
397 
398  infected(row_from, col_from) -= infected_moved;
399  susceptible(row_from, col_from) -= susceptible_moved;
400  total_hosts(row_from, col_from) -= total_hosts_moved;
401  total_exposed(row_from, col_from) -= exposed_moved;
402  resistant(row_from, col_from) -= resistant_moved;
403  infected(row_to, col_to) += infected_moved;
404  susceptible(row_to, col_to) += susceptible_moved;
405  total_hosts(row_to, col_to) += total_hosts_moved;
406  total_exposed(row_to, col_to) += exposed_moved;
407  resistant(row_to, col_to) += resistant_moved;
408  }
409  return movements.size();
410  }
411 
421  void generate(
422  IntegerRaster& dispersers,
423  const IntegerRaster& infected,
424  bool weather,
425  const FloatRaster& weather_coefficient,
426  double reproductive_rate,
427  const std::vector<std::vector<int>>& suitable_cells)
428  {
429  double lambda = reproductive_rate;
430  for (auto indices : suitable_cells) {
431  int i = indices[0];
432  int j = indices[1];
433  if (infected(i, j) > 0) {
434  if (weather)
435  lambda = reproductive_rate * weather_coefficient(i, j);
436  int dispersers_from_cell = 0;
437  if (dispersers_stochasticity_) {
438  std::poisson_distribution<int> distribution(lambda);
439  for (int k = 0; k < infected(i, j); k++) {
440  dispersers_from_cell += distribution(generator_);
441  }
442  }
443  else {
444  dispersers_from_cell = lambda * infected(i, j);
445  }
446  dispersers(i, j) = dispersers_from_cell;
447  }
448  else {
449  dispersers(i, j) = 0;
450  }
451  }
452  }
453 
498  template<typename DispersalKernel>
499  void disperse(
500  const IntegerRaster& dispersers,
501  IntegerRaster& susceptible,
502  IntegerRaster& exposed_or_infected,
503  IntegerRaster& mortality_tracker,
504  const IntegerRaster& total_populations,
505  IntegerRaster& total_exposed,
506  std::vector<std::tuple<int, int>>& outside_dispersers,
507  bool weather,
508  const FloatRaster& weather_coefficient,
509  DispersalKernel& dispersal_kernel,
510  const std::vector<std::vector<int>>& suitable_cells,
511  double establishment_probability = 0.5)
512  {
513  std::uniform_real_distribution<double> distribution_uniform(0.0, 1.0);
514  int row;
515  int col;
516 
517  for (auto indices : suitable_cells) {
518  int i = indices[0];
519  int j = indices[1];
520  if (dispersers(i, j) > 0) {
521  for (int k = 0; k < dispersers(i, j); k++) {
522  std::tie(row, col) = dispersal_kernel(generator_, i, j);
523  if (row < 0 || row >= rows_ || col < 0 || col >= cols_) {
524  // export dispersers dispersed outside of modeled area
525  outside_dispersers.emplace_back(std::make_tuple(row, col));
526  continue;
527  }
528  if (susceptible(row, col) > 0) {
529  double probability_of_establishment =
530  (double)(susceptible(row, col))
531  / total_populations(row, col);
532  double establishment_tester = 1 - establishment_probability;
533  if (establishment_stochasticity_)
534  establishment_tester = distribution_uniform(generator_);
535 
536  if (weather)
537  probability_of_establishment *= weather_coefficient(i, j);
538  if (establishment_tester < probability_of_establishment) {
539  exposed_or_infected(row, col) += 1;
540  susceptible(row, col) -= 1;
541  if (model_type_ == ModelType::SusceptibleInfected) {
542  mortality_tracker(row, col) += 1;
543  }
544  else if (
545  model_type_ == ModelType::SusceptibleExposedInfected) {
546  total_exposed(row, col) += 1;
547  }
548  else {
549  throw std::runtime_error(
550  "Unknown ModelType value in "
551  "Simulation::disperse()");
552  }
553  }
554  }
555  }
556  }
557  }
558  }
559 
590  template<typename DispersalKernel>
592  IntegerRaster& susceptible,
593  IntegerRaster& infected,
594  const IntegerRaster& total_hosts,
595  std::vector<std::tuple<int, int>>& outside_dispersers,
596  DispersalKernel& dispersal_kernel,
597  const std::vector<std::vector<int>>& suitable_cells,
598  double overpopulation_percentage,
599  double leaving_percentage)
600  {
601  struct Move
602  {
603  RasterIndex row;
604  RasterIndex col;
605  int count;
606  };
607  std::vector<Move> moves;
608 
609  // Identify the moves. Remove from source cells.
610  for (auto indices : suitable_cells) {
611  int i = indices[0];
612  int j = indices[1];
613  int original_count = infected(i, j);
614  // No move with only one infected host (one unit).
615  if (original_count <= 1)
616  continue;
617  // r = I / (I + S)
618  // r = I / (I + S + E_1 + E_2 + ...)
619  double ratio = original_count / double(total_hosts(i, j));
620  if (ratio >= overpopulation_percentage) {
621  int row;
622  int col;
623  std::tie(row, col) = dispersal_kernel(generator_, i, j);
624  // for leaving_percentage == 0.5
625  // 2 infected -> 1 leaving
626  // 3 infected -> 1 leaving
627  int leaving = original_count * leaving_percentage;
628  susceptible(i, j) += leaving;
629  infected(i, j) -= leaving;
630  if (row < 0 || row >= rows_ || col < 0 || col >= cols_) {
631  // Collect pests dispersed outside of modeled area.
632  outside_dispersers.reserve(outside_dispersers.size() + leaving);
633  for (int pest = 0; pest < leaving; ++pest)
634  outside_dispersers.emplace_back(row, col);
635  continue;
636  }
637  // Doing the move here would create inconsistent results as some
638  // target cells would be evaluated after the moved pest arrived,
639  // possibly triggering another move.
640  // So, instead, we just collect them and apply later. (The pest is in
641  // the air when in the moves vector.)
642  moves.push_back(Move({row, col, leaving}));
643  }
644  }
645  // Perform the moves to target cells.
646  for (const auto& move : moves) {
647  RasterIndex row = move.row;
648  RasterIndex col = move.col;
649  int leaving = move.count;
650  // The target cell can accept all.
651  if (susceptible(row, col) >= leaving) {
652  susceptible(row, col) -= leaving;
653  infected(row, col) += leaving;
654  }
655  // More pests than the target cell can accept.
656  // This can happen if there is simply not enough S hosts to accomodate all
657  // the pests moving from the source or if multiple sources end up in the
658  // same target cell and there is not enough S hosts to accomodate all of
659  // them. The pests just disappear in both cases.
660  else {
661  leaving = susceptible(row, col);
662  susceptible(row, col) -= leaving;
663  infected(row, col) += leaving;
664  }
665  }
666  }
667 
711  unsigned step,
712  std::vector<IntegerRaster>& exposed,
713  IntegerRaster& infected,
714  IntegerRaster& mortality_tracker,
715  IntegerRaster& total_exposed)
716  {
717  if (model_type_ == ModelType::SusceptibleExposedInfected) {
718  if (step >= latency_period_) {
719  // Oldest item needs to be in the front
720  auto& oldest = exposed.front();
721  // Move hosts to infected raster
722  infected += oldest;
723  mortality_tracker += oldest;
724  total_exposed -= oldest;
725  // Reset the raster
726  // (hosts moved from the raster)
727  oldest.fill(0);
728  }
729  // Age the items and the used one to the back
730  // elements go one position to the left
731  // new oldest goes to the front
732  // old oldest goes to the back
733  rotate_left_by_one(exposed);
734  }
735  else if (model_type_ == ModelType::SusceptibleInfected) {
736  // no-op
737  }
738  else {
739  throw std::runtime_error(
740  "Unknown ModelType value in Simulation::infect_exposed()");
741  }
742  }
743 
770  template<typename DispersalKernel>
772  unsigned step,
773  const IntegerRaster& dispersers,
774  IntegerRaster& susceptible,
775  std::vector<IntegerRaster>& exposed,
776  IntegerRaster& infected,
777  IntegerRaster& mortality_tracker,
778  const IntegerRaster& total_populations,
779  IntegerRaster& total_exposed,
780  std::vector<std::tuple<int, int>>& outside_dispersers,
781  bool weather,
782  const FloatRaster& weather_coefficient,
783  DispersalKernel& dispersal_kernel,
784  const std::vector<std::vector<int>>& suitable_cells,
785  double establishment_probability = 0.5)
786  {
787  auto* infected_or_exposed = &infected;
788  if (model_type_ == ModelType::SusceptibleExposedInfected) {
789  // The empty - not yet exposed - raster is in the back
790  // and will become youngest exposed one.
791  infected_or_exposed = &exposed.back();
792  }
793  this->disperse(
794  dispersers,
795  susceptible,
796  *infected_or_exposed,
797  mortality_tracker,
798  total_populations,
799  total_exposed,
800  outside_dispersers,
801  weather,
802  weather_coefficient,
803  dispersal_kernel,
804  suitable_cells,
805  establishment_probability);
806  if (model_type_ == ModelType::SusceptibleExposedInfected) {
807  this->infect_exposed(
808  step, exposed, infected, mortality_tracker, total_exposed);
809  }
810  }
811 };
812 
813 } // namespace pops
814 
815 #endif // POPS_SIMULATION_HPP
pops::Simulation::disperse_and_infect
void disperse_and_infect(unsigned step, const IntegerRaster &dispersers, IntegerRaster &susceptible, std::vector< IntegerRaster > &exposed, IntegerRaster &infected, IntegerRaster &mortality_tracker, const IntegerRaster &total_populations, IntegerRaster &total_exposed, std::vector< std::tuple< int, int >> &outside_dispersers, bool weather, const FloatRaster &weather_coefficient, DispersalKernel &dispersal_kernel, const std::vector< std::vector< int >> &suitable_cells, double establishment_probability=0.5)
Disperse, expose, and infect based on dispersers.
Definition: simulation.hpp:771
pops::Simulation::Simulation
Simulation(unsigned random_seed, RasterIndex rows, RasterIndex cols, ModelType model_type=ModelType::SusceptibleInfected, unsigned latency_period=0, bool dispersers_stochasticity=true, bool establishment_stochasticity=true, bool movement_stochasticity=true)
Creates simulation object and seeds the internal random number generator.
Definition: simulation.hpp:157
pops::draw_n_from_v
std::vector< int > draw_n_from_v(std::vector< int > v, unsigned n, Generator &generator)
Draws n elements from a vector.
Definition: simulation.hpp:48
pops::Simulation::infect_exposed
void infect_exposed(unsigned step, std::vector< IntegerRaster > &exposed, IntegerRaster &infected, IntegerRaster &mortality_tracker, IntegerRaster &total_exposed)
Infect exposed hosts (E to I transition in the SEI model)
Definition: simulation.hpp:710
utils.hpp
pops::Simulation::movement
unsigned movement(IntegerRaster &infected, IntegerRaster &susceptible, std::vector< IntegerRaster > &mortality_tracker_vector, std::vector< IntegerRaster > &exposed, IntegerRaster &resistant, IntegerRaster &total_hosts, IntegerRaster &total_exposed, unsigned step, unsigned last_index, const std::vector< std::vector< int >> &movements, std::vector< unsigned > movement_schedule, std::vector< std::vector< int >> &suitable_cells)
Moves hosts from one location to another.
Definition: simulation.hpp:286
indices
Definition: utils.hpp:97
pops::ModelType::SusceptibleInfected
@ SusceptibleInfected
SI (susceptible - infected)
pops::Simulation
The main class to control the spread simulation.
Definition: simulation.hpp:126
pops::Simulation::move_overpopulated_pests
void move_overpopulated_pests(IntegerRaster &susceptible, IntegerRaster &infected, const IntegerRaster &total_hosts, std::vector< std::tuple< int, int >> &outside_dispersers, DispersalKernel &dispersal_kernel, const std::vector< std::vector< int >> &suitable_cells, double overpopulation_percentage, double leaving_percentage)
Move overflowing pest population to other hosts.
Definition: simulation.hpp:591
pops::Simulation::generate
void generate(IntegerRaster &dispersers, const IntegerRaster &infected, bool weather, const FloatRaster &weather_coefficient, double reproductive_rate, const std::vector< std::vector< int >> &suitable_cells)
Generates dispersers based on infected.
Definition: simulation.hpp:421
pops::Simulation::disperse
void disperse(const IntegerRaster &dispersers, IntegerRaster &susceptible, IntegerRaster &exposed_or_infected, IntegerRaster &mortality_tracker, const IntegerRaster &total_populations, IntegerRaster &total_exposed, std::vector< std::tuple< int, int >> &outside_dispersers, bool weather, const FloatRaster &weather_coefficient, DispersalKernel &dispersal_kernel, const std::vector< std::vector< int >> &suitable_cells, double establishment_probability=0.5)
Creates dispersal locations for the dispersing individuals.
Definition: simulation.hpp:499
pops::Simulation::remove
void remove(IntegerRaster &infected, IntegerRaster &susceptible, const FloatRaster &temperature, double lethal_temperature, const std::vector< std::vector< int >> &suitable_cells)
removes infected based on min or max temperature tolerance
Definition: simulation.hpp:187
pops::rotate_left_by_one
void rotate_left_by_one(Container &container)
Rotate elements in a container to the left by one.
Definition: simulation.hpp:40
pops
Definition: cauchy_kernel.hpp:25
pops::NaturalAnthropogenicDispersalKernel
Dispersal kernel template for natural and anthropogenic distance dispersal.
Definition: natural_anthropogenic_kernel.hpp:43
pops::model_type_from_string
ModelType model_type_from_string(const std::string &text)
Get a corresponding enum value for a string which is a model type name.
Definition: simulation.hpp:71
pops::ModelType::SusceptibleExposedInfected
@ SusceptibleExposedInfected
SEI (susceptible - exposed - infected)
pops::Simulation::mortality
void mortality(IntegerRaster &infected, IntegerRaster &total_hosts, double mortality_rate, int mortality_time_lag, IntegerRaster &died, std::vector< IntegerRaster > &mortality_tracker_vector, const std::vector< std::vector< int >> &suitable_cells)
kills infected hosts based on mortality rate and timing.
Definition: simulation.hpp:223
pops::ModelType
ModelType
The type of a epidemiological model (SI or SEI)
Definition: simulation.hpp:60
pops::Simulation::Simulation
Simulation()=delete