forked from jakmeier/covid-sim
/
lib.rs
138 lines (132 loc) · 3.97 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct Simulation {
w: usize,
h: usize,
radius: usize,
recovery: u8,
infection_rate: f32,
death_rate: f32,
rng: SmallRng,
}
#[wasm_bindgen]
impl Simulation {
#[wasm_bindgen(constructor)]
pub fn new() -> Simulation {
// This makes Rust print a useful stack trace in the console when it panics.
// Should only be used for debugging. This can easily be achieved with conditional compilation flags.
// The line below only gets executed when running in development mode.
#[cfg(debug_assertions)]
console_error_panic_hook::set_once();
// Create small, cheap to initialize and fast RNG with a random seed.
// The randomness is supplied by the operating system.
let rng = SmallRng::from_entropy();
Simulation {
w: 0,
h: 0,
radius: 1,
recovery: 1,
infection_rate: 0.01,
death_rate: 0.01,
rng,
}
}
pub fn reconfigure(
&mut self,
w: usize,
h: usize,
radius: usize,
recovery: u8,
infection_rate: f32,
death_rate: f32,
) {
self.w = w;
self.h = h;
self.radius = radius;
self.recovery = recovery;
self.infection_rate = infection_rate;
self.death_rate = death_rate;
}
}
#[derive(Clone, Copy)]
enum InfectionStatus {
Healthy,
Infected(u8),
Immune,
Dead,
}
use InfectionStatus::*;
#[wasm_bindgen]
impl Simulation {
pub fn next_day(&mut self, input: &[u8], output: &mut [u8]) {
for x in 0..self.w {
for y in 0..self.h {
let current = self.get(input, x, y);
let mut next = current;
match current {
Healthy => {
if self.chance_to_catch_covid_today(input, x, y) > self.rng.gen() {
next = Infected(1);
}
}
Infected(days) => {
if self.death_rate > self.rng.gen() {
next = Dead;
} else if days >= self.recovery {
next = Immune;
} else {
next = Infected(days + 1);
}
}
Dead | Immune => { /* NOP */ }
}
self.set(output, x, y, next);
}
}
}
fn chance_to_catch_covid_today(&self, input: &[u8], x: usize, y: usize) -> f32 {
let mut infected_neighbours = 0;
let start_x = x.saturating_sub(self.radius);
let start_y = y.saturating_sub(self.radius);
let end_x = (x + self.radius + 1).min(self.w);
let end_y = (y + self.radius + 1).min(self.h);
for j in start_y..end_y {
for i in start_x..end_x {
if let Infected(_days) = self.get(input, i, j) {
infected_neighbours += 1;
}
}
}
let p_healthy = (1.0 - self.infection_rate).powi(infected_neighbours);
1.0 - p_healthy
}
}
impl Simulation {
fn get(&self, array: &[u8], x: usize, y: usize) -> InfectionStatus {
InfectionStatus::from_u8(array[x + y * self.w])
}
fn set(&self, array: &mut [u8], x: usize, y: usize, status: InfectionStatus) {
array[x + y * self.w] = status.to_u8();
}
}
impl InfectionStatus {
fn from_u8(num: u8) -> Self {
match num {
0 => Healthy,
1...65 => Infected(num),
100 => Immune,
101 => Dead,
_ => panic!("Invalid infection status: {}", num),
}
}
fn to_u8(self) -> u8 {
match self {
Healthy => 0,
Infected(days) => days,
Immune => 100,
Dead => 101,
}
}
}