Advertisement
Guest User

Herd immunity simulator

a guest
Jan 26th, 2021
105
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 12.08 KB | None | 0 0
  1. #include <thread>
  2. #include <chrono>
  3. #include <iostream>
  4. #include <vector>
  5. #include <set>
  6. #include <string>
  7. #include <random>
  8. #include <iomanip>
  9.  
  10. enum State {
  11.     Uninfected,
  12.     Infected,
  13.     Immune,
  14.     Dead
  15. };
  16.  
  17. struct Person {
  18.     bool  vulnerable;
  19.     State state;
  20.     unsigned x;
  21.     unsigned y;
  22.     // Precomputed neighboring people
  23.     std::vector<Person*> neighbors;
  24.     Person()
  25.         : vulnerable()
  26.         , state(Uninfected)
  27.         , x()
  28.         , y()
  29.     {
  30.     }
  31. };
  32.  
  33. // Escape codes, the lazy way
  34. void CLR() { std::cout << "\x1b[H\x1b[J"; }
  35. void CUP(int x, int y) { std::cout << "\x1b[" << (y+1) << ";" << (x+1) << "H"; }
  36. void EL() { std::cout << "\x1b[K"; };
  37. void RGB(unsigned r, unsigned g, unsigned b) {
  38.     r%=6; g%=6; b%=6;
  39.     std::cout << "\x1b[38;5;" << ((36*r+6*g+b)+16) << "m";
  40. }
  41.  
  42. struct Environment {
  43.     unsigned width;
  44.     unsigned height;
  45.     // Don't need to seed this; getting the same results
  46.     // for the same run is actually better for us.
  47.     std::mt19937 rng;
  48.     // The actual people
  49.     std::vector<Person> people;
  50.     // Number of iterations
  51.     unsigned int rounds;
  52.     // Total deaths
  53.     unsigned int deaths;
  54.     // Infected people this round
  55.     unsigned int infections;
  56.     // Total immune
  57.     unsigned int immunizations;
  58.     // Deaths since last round
  59.     unsigned int death_delta;
  60.     // Immunized since last round
  61.     unsigned int immunization_delta;
  62.     Environment(unsigned w, unsigned h)
  63.         : width(w)
  64.         , height(h)
  65.         , rng()
  66.         , rounds()
  67.         , deaths()
  68.         , infections()
  69.         , immunizations()
  70.         , death_delta()
  71.         , immunization_delta()
  72.     {
  73.     }
  74.     // Convert x,y to the 1D index into our land vector
  75.     unsigned xy_ndx(int x, int y)
  76.     {
  77.         // ...allows small negative deltas with correct wraparound
  78.         //    (makes grid a torus)
  79.         if (x<0) x+=width;
  80.         if (y<0) y+=height;
  81.         return (x%width)+(y%height)*width;
  82.     }
  83.     // Add vulnerable
  84.     void add_vulnerable(unsigned int range, unsigned to_add)
  85.     {
  86.         // Build the land array back up
  87.         std::vector<Person*> land(width*height);
  88.         std::uniform_int_distribution<unsigned> xgen(0, width-1);
  89.         std::uniform_int_distribution<unsigned> ygen(0, height-1);
  90.         std::uniform_int_distribution<unsigned> ngen(0, population-1);
  91.  
  92.         std::vector<Person*> land(width*height);
  93.         for (auto &p : person)
  94.         {
  95.             land[xy_ndx(p.x,p.y)]=&p;
  96.         }
  97.         unsigned int start = people.size();
  98.         unsigned int end = start + to_add;
  99.         people.resize(end);
  100.         for (auto i=start; i<end; ++i)
  101.         {
  102.             for(;;)
  103.             {
  104.                 auto x = xgen(rng), y = ygen(rng);
  105.                 if (land[xy_ndx(x,y)]) continue;
  106.                 people[i].x = x;
  107.                 people[i].y = y;
  108.                 people[i].state = Uninfected;
  109.                 people[i].vulnerable = true;
  110.             }
  111.         }
  112.         unsigned sq = range*range;
  113.         std::vector<std::pair<int,int>> deltas;
  114.         for (int x=0, xe=range; x<xe; ++x)
  115.             for (int y=0, ye=range; y<ye; ++y)
  116.             {
  117.                 // skip origin
  118.                 if ((x==0)&&(y==0)) continue;
  119.                 if (x*x+y*y>sq) continue;
  120.                 // Add all deltas
  121.                 deltas.push_back(std::make_pair( x,  y));
  122.                 deltas.push_back(std::make_pair(-x,  y));
  123.                 deltas.push_back(std::make_pair( x, -y));
  124.                 deltas.push_back(std::make_pair(-x, -y));
  125.             }
  126.         // For each new vulnerable added...
  127.         for (auto i=start; i<end; ++i)
  128.         {
  129.             auto &tp = people[i];
  130.             auto x = tp.x;
  131.             auto y = tp.y;
  132.             // For each delta
  133.             for (auto &d : deltas)
  134.             {
  135.                 auto& p2 = land[xy_ndx(x+d.first, y+d.second)]
  136.                     // Now it's possible they're already linked, so check first
  137.                     if (std::find(tp.neighbors.begin(), tp.neighbors.end(), p2)!=tp.neighbors.end())
  138.                         continue;
  139.                 tp.neighbors.push_back(&p2);
  140.                 p2.neighbors.push_back(&p1);
  141.             }
  142.         }
  143.     }
  144.     // Initial array population
  145.     void populate(unsigned population, unsigned range, unsigned vulnerable, unsigned infected, unsigned immune)
  146.     {
  147.         infections = infected;
  148.         immunizations = immune;
  149.         // Temporarily allocate pointers for all land
  150.         std::vector<Person*> land(width*height);
  151.         // for x coordinate (in 2d environment)
  152.         std::uniform_int_distribution<unsigned> xgen(0, width-1);
  153.         // for y coordinate (in 2d environment)
  154.         std::uniform_int_distribution<unsigned> ygen(0, height-1);
  155.         // for n coordinate (index into array of size population)
  156.         std::uniform_int_distribution<unsigned> ngen(0, population-1);
  157.         people.resize(population);
  158.         for (auto i=0; i<population; ++i)
  159.         {
  160.             // For looping if x,y is already populated
  161.             for(;;)
  162.             {
  163.                 auto x = xgen(rng), y = ygen(rng);
  164.                 if (land[xy_ndx(x,y)]) continue;
  165.                 // Set up person i
  166.                 Person* tp = &people[i];
  167.                 land[xy_ndx(x,y)] = tp;
  168.                 tp->x = x;
  169.                 tp->y = y;
  170.                 // people list already random; infect the first n people
  171.                 // where n=infected, then mark the next o immune
  172.                 // where o=immune.
  173.                 if (i<infected) tp->state = Infected;
  174.                 else if (i<infected+immune) tp->state = Immune;
  175.                 // and here we exit to outer loop
  176.                 break;
  177.             }
  178.         }
  179.         // Now mark people vulnerable; do this in another loop
  180.         // to mixing vulnerable people with infected
  181.         for (auto i=0; i<vulnerable; ++i)
  182.         {
  183.             for(;;) {
  184.                 auto n = ngen(rng);
  185.                 if (people[n].vulnerable) continue;
  186.                 // comment this out to "vaccinate" the vulnerable
  187.                 if (people[n].state==Immune) continue;
  188.  
  189.                 people[n].vulnerable = true;
  190.                 break;
  191.             }
  192.         }
  193.         // Pre-calculate the integral delta squares one semicircle down;
  194.         // this way we have a smaller set to iterate through.
  195.         unsigned sq = range*range;
  196.         std::vector<std::pair<int,int>> deltas;
  197.         // Loop through just one quadrant and flip while looping
  198.         for (int x=0, xe=range; x<xe; ++x)
  199.             for (int y=0, ye=range; y<ye; ++y)
  200.             {
  201.                 // skip origin
  202.                 if ((x==0)&&(y==0)) continue;
  203.                 if (x*x+y*y>sq) continue;
  204.                 // Add this and the x-reflected point
  205.                 deltas.push_back(std::make_pair(x, y));
  206.                 deltas.push_back(std::make_pair(-x, y));
  207.             }
  208.         // For each person on the grid...
  209.         for (int x=0; x<width; ++x)
  210.             for (int y=0; y<height; ++y)
  211.                 if (land[xy_ndx(x,y)])
  212.                     // ...find all people "below" that are our neighbors...
  213.                     for (auto &d : deltas)
  214.                     {
  215.                         if (land[xy_ndx(x+d.first, y+d.second)])
  216.                         {
  217.                             auto p1=land[xy_ndx(x,y)];
  218.                             auto p2=land[xy_ndx(x+d.first, y+d.second)];
  219.                             // ...and link in both directions
  220.                             p1->neighbors.push_back(p2);
  221.                             p2->neighbors.push_back(p1);
  222.                         }
  223.                     }
  224.     }
  225.     // Draw the background as dots
  226.     void bg()
  227.     {
  228.         std::string row(width, '.');
  229.         RGB(2,2,2);
  230.         for (unsigned y=0; y<height; ++y) {
  231.             CUP(0, y+1);
  232.             std::cout << row;
  233.         }
  234.     }
  235.     // Show current state
  236.     void show()
  237.     {
  238.         CUP(0,0);
  239.         EL();
  240.         RGB(5,5,5);
  241.         // Header stats
  242.         std::cout
  243.             << "Round " << std::setw(4) << rounds
  244.             << "|Deaths " << std::setw(4) << deaths
  245.             << " +" << death_delta
  246.             << "|Infected " << std::setw(4) << infections
  247.             << "|Immunized " << std::setw(4) << immunizations
  248.             << " +" << immunization_delta
  249.             ;
  250.         // Render each person
  251.         for (auto &p : people)
  252.         {
  253.             CUP(p.x, p.y+1);
  254.             switch (p.state)
  255.             {
  256.                 case Uninfected: RGB(1, 1, 5); std::cout << "U"; break;
  257.                 case Infected:   RGB(5, 5, 1); std::cout << "S"; break;
  258.                 case Immune:     RGB(5, 3, 0); std::cout << "I"; break;
  259.                 case Dead:       RGB(3, 3, 4); std::cout << "X"; break;
  260.             }
  261.         }
  262.         // Move cursor to end and flush;
  263.         // we use "endl" here so we can see lines
  264.         // in captured data with tee (for diagnostics)
  265.         CUP(width,height+1); std::cout << std::endl;
  266.     }
  267.     // Evolution step
  268.     void evolve()
  269.     {
  270.         ++rounds;
  271.         death_delta = 0;
  272.         // To avoid "smearing", we collect persons
  273.         // who need to transition, then transition
  274.         // only after the entire loop
  275.         std::set<Person*> died;
  276.         std::set<Person*> immunized;
  277.         std::set<Person*> infected;
  278.         for (auto& p : people)
  279.         {
  280.             // If a person is initially infected,
  281.             if (p.state == Infected)
  282.             {
  283.                 // If they are vulnerable,
  284.                 if (p.vulnerable)
  285.                 {
  286.                     // ...then they die
  287.                     died.insert(&p);
  288.                 }
  289.                 // And if they are not,
  290.                 else
  291.                 {
  292.                     // ...then they become immune
  293.                     immunized.insert(&p);
  294.                 }
  295.                 // And for each neighbor in the infection range...
  296.                 for (auto &neighbor : p.neighbors)
  297.                     // If that neighbor is uninfected,
  298.                     if (neighbor->state==Uninfected)
  299.                     {
  300.                         // ...then they get sick
  301.                         infected.insert(neighbor);
  302.                     }
  303.                 // Else if they are infected, they changed above
  304.                 // Else if they are immune, they don't get sick
  305.                 // Else (they are dead) nothing happens.
  306.             }
  307.         }
  308.         // Stats
  309.         death_delta = died.size();
  310.         immunization_delta = immunized.size();
  311.         infections = infected.size();
  312.         deaths += death_delta;
  313.         immunizations += immunization_delta;
  314.         for (auto &p : died) p->state = Dead;
  315.         for (auto &p : immunized) p->state = Immune;
  316.         for (auto &p : infected) p->state = Infected;
  317.     }
  318. };
  319.  
  320. int main(int argc, const char* argv[])
  321. {
  322.     // Parse arguments; since this is a one-time demo, we don't need nice,
  323.     // just a way to input the stuff.
  324.  
  325.     // Start by sucking char*'s into vector of strings (and dropping argv[0])
  326.     std::vector<std::string> args(argv+1, argv+argc);
  327.     if (args.size()<7)
  328.         return 1;
  329.     // Arguments, in order:
  330.     unsigned width           = std::stoi(args[0]);
  331.     unsigned height          = std::stoi(args[1]);
  332.     unsigned population      = std::stoi(args[2]);
  333.     unsigned range           = std::stoi(args[3]);
  334.     unsigned num_vulnerable  = std::stoi(args[4]);
  335.     unsigned num_infected    = std::stoi(args[5]);
  336.     unsigned num_immune      = std::stoi(args[6]);
  337.     // Create a width x height world
  338.     Environment world(width, height);
  339.     // Populate per above parameters
  340.     world.populate(population, range, num_vulnerable, num_infected, num_immune);
  341.     // Clear the screen...
  342.     CLR();
  343.     // Prime the bg and show initial state
  344.     world.bg();
  345.     world.show();
  346.     for (;;) {
  347.         std::this_thread::sleep_for(std::chrono::milliseconds(500));
  348.         world.evolve();
  349.         world.show();
  350.         // End at eradication
  351.         if (world.infections==0) break;
  352.     }
  353. }
  354.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement