Published
Edited
May 11, 2020
6 forks
58 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
sim
Insert cell
sim_results = sim.run_simulation(simulation_parameters, initial_infections)
Insert cell
sim = setup_infection_sim_full(data_gen_parameters)
Insert cell
function setup_infection_sim_full(data_params){
// Generate network data
const {nodes, edges, node_quadtree} = generate_network_data(
data_params.num_nodes.val,
build_poisson_mixture([
{mean: data_params.avg_num_edges.val, size: 1 - data_params.prop_popular_nodes.val},
{mean: data_params.avg_num_edges_pop.val, size: data_params.prop_popular_nodes.val}
])
);

function* run_simulation(sim_params, initial_infections = [], sim_speed = 1){

let time = 1;
const hist = [{
total_infections: initial_infections.length,
new_infections: initial_infections,
n_new: initial_infections.length,
potential_infectors: initial_infections.length,
time: time++
}];

// Reset all nodes before starting
nodes.forEach(node => {
node.infected = false;
node.inactive = false;
node.time_infected = -1;
node.uninfected_neighbors = [...node.neighbors];
});

// Setup the initial infecting nodes
initial_infections.forEach(id => {
const node = nodes[id];
node.infected = true;
node.time_infected = time;

// Create an array of potential neighbors to infect, make sure to not include already infected nodes
node.uninfected_neighbors = node.neighbors.filter(n => !(initial_infections.includes(n.id)));
});

// State vectors that keep track of what nodes are in what states
const potential_infectors = new Set(initial_infections);
const infections = new Set(initial_infections);

while(potential_infectors.size > 0 && infections.size < nodes.length) {
const no_longer_infecting = [];
const new_infections = new Set();

const expose_node = node => {
const was_infected = Math.random() < sim_params.prob_of_infect.val;
if(was_infected) new_infections.add(node.id);
};

potential_infectors.forEach(id => {
const node = nodes[id];

// Exposes all the node's neighbors
node.uninfected_neighbors.forEach(expose_node);

// Does this node randomly expose a "long edge"?
const has_long_edge = Math.random() < sim_params.prob_of_long_edge.val;
if (has_long_edge){
const random_node = nodes[Math.floor(Math.random() * nodes.length)];
const is_infectable = !(random_node.id === node.id || random_node.infected);
if(is_infectable) expose_node(random_node);
}

// Will node no longer be infections at the next time point?
if(time >= node.time_infected + sim_params.contagious_period.val) no_longer_infecting.push(id);
});

const set_inactive = id => {
nodes[id].inactive = true;
potential_infectors.delete(id);
}

// Remove nodes neighbors infected or have been infected for time period from potential infectors
no_longer_infecting.forEach(set_inactive);

// Run through newly infected nodes and set to infected
new_infections.forEach( id => {
infections.add(id);

const node = nodes[id];
node.infected = true;
node.time_infected = time;

// Create an array of potential neighbors to infect, make sure to not include already infected nodes
node.uninfected_neighbors = node.neighbors.filter(n => !(n.infected || new_infections.has(n.id)));

// Node can only infect others if it has neighbors that are not already infected
if(node.uninfected_neighbors.length !== 0) potential_infectors.add(id);

// Go to all of this node's neighbors and remove itself from their list of nodes they could infect
node.neighbors.forEach(neighbor => {
neighbor.uninfected_neighbors = neighbor.uninfected_neighbors.filter(n => n.id !== id);

if(neighbor.uninfected_neighbors.length == 0) set_inactive(neighbor.id);
});
});

// Update state history
hist.push({total_infections: infections.size,
new_infections: [...new_infections],
n_new: new_infections.size,
potential_infectors: potential_infectors.size,
time: time});

yield Promises.tick(sim_speed, {ids: [...infections],
history: hist,
timestep: time++,
running: true,
settings: sim_params,
status: "running"});
}
yield Promises.tick(sim_speed, {ids: [...infections],
history: hist,
timestep: hist.length,
settings: sim_params,
status: hist.length == 1 ? "waiting": "finished"});
}

return {nodes,
edges,
node_quadtree,
run_simulation,
settings: data_params};
}
Insert cell
Insert cell
colors = {
const early_infection = "#990000";
const late_infection = "#fc8d59";
const infected = "#e41a1c";
const healthy = "#377eb8";
const safe_edge = "#d9d9d9";
const interactive = "#2690ce";
const inactive = change_brightness(infected, -1);
const newly_infected = change_brightness(infected, 1);
const danger_edge = d3.interpolateHslLong(safe_edge, infected)(0.5);
const inactive_edge = d3.interpolateHslLong(safe_edge, inactive)(0.1);
return {infected,
inactive,
newly_infected,
healthy,
safe_edge,
inactive_edge,
danger_edge,
interactive,
early_infection,
late_infection};
}
Insert cell
Insert cell
mutable data_gen_parameters = ({
num_nodes: {
val: 3000,
min: 1000,
max: 15001,
step: 100,
int: true,
},
avg_num_edges: {
val: 8,
min: 1,
max: 25,
step: 0.1,
},
avg_num_edges_pop: {
val: 25,
min: 10,
max: 100,
step: 0.1,
},
prop_popular_nodes: {
val: 0.2,
min: 0,
max: 1,
step: 0.01,
percent: true,
}
});
Insert cell
mutable simulation_parameters = ({
prob_of_infect: {
val: 0.15,
min: 0.01,
max: 0.99,
start: 0.15,
step: 0.01,
},
prob_of_long_edge:
{
val: 0,
min: 0,
max: 0.2,
start: 0,
step: 0.005,
},
contagious_period: {
val: 10,
min: 1,
max: 30,
start: 10,
step: 1,
int: true,
}
});
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more