The UFZ services GitLab and Mattermost will be unavailable on Monday, December 06 from 06:00 AM to 08:00 AM due to maintenance work.

Commit 941bc3bf authored by Adam Reichold's avatar Adam Reichold
Browse files

WIP: Abuse the vectors measurement to trace the temporal structure of the...

WIP: Abuse the vectors measurement to trace the temporal structure of the contact and transmission networks.
parent 9d38408d
Pipeline #43927 failed with stage
in 5 minutes and 48 seconds
......@@ -6,6 +6,17 @@ version = 3
name = "access"
version = "0.1.0"
[[package]]
name = "ahash"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "autocfg"
version = "1.0.1"
......@@ -28,6 +39,50 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crossbeam-channel"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
dependencies = [
"cfg-if",
"crossbeam-utils",
"lazy_static",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
dependencies = [
"cfg-if",
"lazy_static",
]
[[package]]
name = "dump"
version = "0.1.0"
......@@ -35,6 +90,12 @@ dependencies = [
"model",
]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "experiments"
version = "0.1.0"
......@@ -56,10 +117,35 @@ dependencies = [
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
"rayon",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "kdtree"
version = "0.1.0"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.101"
......@@ -78,8 +164,10 @@ version = "0.1.0"
dependencies = [
"access",
"dump",
"hashbrown",
"memmap2",
"model",
"rayon",
]
[[package]]
......@@ -91,12 +179,22 @@ dependencies = [
"libc",
]
[[package]]
name = "memoffset"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
dependencies = [
"autocfg",
]
[[package]]
name = "model"
version = "0.1.0"
dependencies = [
"access",
"bitmap",
"hashbrown",
"kdtree",
"process",
"queue",
......@@ -117,6 +215,22 @@ dependencies = [
"libm",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "pkg-config"
version = "0.3.19"
......@@ -197,6 +311,43 @@ dependencies = [
"memmap2",
]
[[package]]
name = "rayon"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"lazy_static",
"num_cpus",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
......
......@@ -16,6 +16,7 @@ path = "src/lib.rs"
[dependencies]
access = { path = "libraries/access" }
bitmap = { path = "libraries/bitmap" }
hashbrown = "0.11"
kdtree = { path = "libraries/kdtree" }
process = { path = "libraries/process" }
queue = { path = "libraries/queue" }
......
......@@ -61,6 +61,8 @@ pub fn sample(params: Params, duration: Tick, interval: Tick) {
save(&model.world, buffer);
},
);
model.world.vectors.clear();
}
});
}
......
......@@ -99,6 +99,8 @@ unsafe impl Dump for InsectSpecies {}
unsafe impl Dump for usize {}
unsafe impl Dump for u32 {}
unsafe impl Dump for u64 {}
unsafe impl Dump for Tick {}
......
......@@ -8,5 +8,7 @@ edition = "2018"
[dependencies]
access = { path = "../libraries/access" }
dump = { path = "../libraries/dump" }
hashbrown = { version = "0.11", features = ["rayon"] }
memmap2 = "0.3"
model = { path = ".." }
rayon = "1.5"
......@@ -34,12 +34,12 @@ fn main() {
let mut contaminated = HashMap::<FlowerPatchSpecies, (usize, usize)>::new();
for (flower_patch, flowers) in flower_patches(worlds) {
for (flower_patch, _flowers) in flower_patches(worlds) {
let (all_flowers, all_contaminated_flowers) =
contaminated.entry(flower_patch.species).or_default();
*all_flowers += flower_patch.flowers as usize;
*all_contaminated_flowers += flower_patch.contaminated_flowers.count(flowers) as usize;
*all_contaminated_flowers += flower_patch.contaminated_flowers.len();
}
let mut contaminated = contaminated.into_iter().collect::<Vec<_>>();
......
use std::array::IntoIter;
use std::env::args_os;
use std::io::{stdout, BufWriter, Write};
use access::dictionary;
use measurements::collect;
use model::Vectors;
fn main() {
let mut vectors: Vectors = Default::default();
let stdout = stdout();
let mut stdout = BufWriter::with_capacity(1024 * 1024, stdout.lock());
collect(args_os().nth(1).unwrap(), |_tick, worlds| {
vectors =
worlds
.iter()
.map(|world| &world.vectors)
.fold(Default::default(), |mut acc, val| {
for (key, val) in val {
*dictionary(&mut acc, *key) += val;
}
for vectors in worlds.iter().map(|world| &world.vectors) {
for &(time, src_node, dst_node) in vectors {
// writeln!("{}\t{}\t{}", time, src_node, dst_node);
acc
});
});
let all_infections = vectors.iter().map(|(_, val)| val).sum::<usize>();
let mut flower_patch_species = vectors
.iter()
.map(|((species, _, _), _)| *species)
.collect::<Vec<_>>();
flower_patch_species.sort_unstable();
flower_patch_species.dedup();
let mut insect_species = vectors
.iter()
.flat_map(|((_, contaminated_by_species, infected_species), _)| {
IntoIter::new([*contaminated_by_species, *infected_species])
})
.collect::<Vec<_>>();
insect_species.sort_unstable();
insect_species.dedup();
println!("infections = {}", all_infections);
println!();
let time = time.as_seconds() as u32;
let src_node = src_node as u16;
let dst_node = dst_node as u16;
for flower_patch_species in &flower_patch_species {
println!("vectors =");
for contaminated_by_species in &insect_species {
print!("\t");
for infected_species in &insect_species {
let infections = dictionary(
&mut vectors,
(
*flower_patch_species,
*contaminated_by_species,
*infected_species,
),
);
print!("{:.3} ", *infections as f64 / all_infections as f64);
stdout.write_all(&time.to_ne_bytes()).unwrap();
stdout.write_all(&src_node.to_ne_bytes()).unwrap();
stdout.write_all(&dst_node.to_ne_bytes()).unwrap();
}
println!();
}
println!();
}
});
}
......@@ -59,6 +59,7 @@ pub enum Activity {
\begin{code}
impl Bumblebee {
pub fn new(
id: u32,
params: &Params,
colony_location: ColonyLocation,
species: u8,
......@@ -69,6 +70,7 @@ impl Bumblebee {
let pollen = bumblebee_params.pollen_per_bout * rng.gen();
let insect = Insect::new(
id,
params,
&bumblebee_params.insect,
colony_location.position,
......
......@@ -12,8 +12,8 @@ While this small-scale description is spatially implicit, statistic correlations
\begin{hiddencode}
use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::mem::MaybeUninit;
use hashbrown::HashMap;
use rand::{
distributions::{Distribution, OpenClosed01},
Rng as _,
......@@ -23,7 +23,6 @@ use bitmap::Bitmap;
use kdtree::Object;
use crate::{
insect::Species as InsectSpecies,
params::Params,
space::Vector,
units::{Area, Mass, Tick},
......@@ -40,15 +39,14 @@ Having both a continuous representation of available resources by tracking necta
The auxiliary variable \inlinecode{contaminated\_by\_species} is used to track which insect species was responsible for last contamination of a flower in this patch and does not affect the pathogen dynamics but is used as a micro-observable as described in \autoref{micro-observables}.
\begin{code}
#[derive(Clone, Copy)]
pub struct FlowerPatch {
pub species: Species,
pub nectar: Mass,
pub flowers: u16,
pub nectar_production: Mass,
pub empty_flowers: Bitmap,
pub contaminated_flowers: Bitmap,
pub contaminated_by_species: MaybeUninit<InsectSpecies>,
pub contaminated_flowers: HashMap<u16, u32>,
pub visited_flowers: HashMap<u16, HashMap<u32, Tick>>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
......@@ -76,7 +74,7 @@ impl FlowerPatch {
let nectar_production = flower_patch_params.nectar_production(rng);
let mut empty_flowers = Bitmap::new(flowers1, flowers as usize);
let contaminated_flowers = Bitmap::new(flowers1, flowers as usize);
let contaminated_flowers = HashMap::new();
\end{code}
To decrease the time until an equilibrium between nectar production and consumption is reached, the flower patches are initialised stochastically so that the fraction given by the parameter \inlinecode{empty\_flower\_patches} is empty with a uniformly distributed time-to-refill while the rest is given a uniformly distributed amount of nectar left.
......@@ -117,7 +115,7 @@ For the non-depleted flower patches, the discrete representation of available ne
nectar_production,
empty_flowers,
contaminated_flowers,
contaminated_by_species: MaybeUninit::uninit(),
visited_flowers: Default::default(),
}
}
}
......@@ -170,11 +168,9 @@ impl Event {
flower_patch,
flower,
}) if *happens_at <= tick => {
flower_patches[flower_patch.get()].contaminated_flowers.set(
flowers,
*flower as usize,
false,
);
flower_patches[flower_patch.get()]
.contaminated_flowers
.remove(flower);
events.pop();
}
......
......@@ -72,7 +72,7 @@ During initialisation \marginpar{initialisation}, a species within the hoverfly
\begin{code}
impl Hoverfly {
pub fn new(params: &Params, position: Vector, rng: &mut Rng) -> Self {
pub fn new(id: u32, params: &Params, position: Vector, rng: &mut Rng) -> Self {
let species = params.init.hoverfly_species.sample(rng);
let hoverfly_params = &params.hoverflies[species as usize];
......@@ -80,6 +80,7 @@ impl Hoverfly {
let pollen = hoverfly_params.pollen_per_clutch * rng.gen();
let insect = Insect::new(
id,
params,
&hoverfly_params.insect,
position,
......
......@@ -182,10 +182,13 @@ As entities are identified by the rank of the process they are located at and th
Hoverflies are mobile entities which perform their reproductive activities using the stationary clutch entities. Clutches are placed randomly, indexed spatially and discard if they do not support at least a single clutch of eggs in the same manner as the flower patches.
\begin{code}
let mut id = 0;
let mut hoverflies = Vec::new();
for _ in 0..params.init.hoverflies {
let hoverfly = Hoverfly::new(&params, position(&mut rng), &mut rng);
let hoverfly = Hoverfly::new(id, &params, position(&mut rng), &mut rng);
id += 1;
let dest = grid.location(&hoverfly.insect.position);
......@@ -238,7 +241,8 @@ Solitary bees are mobile entities which perform their reproductive activities us
let mut solitary_bees = Vec::new();
for _ in 0..params.init.solitary_bees {
let solitary_bee = SolitaryBee::new(&params, position(&mut rng), &mut rng);
let solitary_bee = SolitaryBee::new(id, &params, position(&mut rng), &mut rng);
id += 1;
let dest = grid.location(&solitary_bee.insect.position);
......@@ -306,7 +310,9 @@ Bumblebees are mobile entities which are always associated to single colony. Hen
}
for _ in 0..colony.capacity {
let bumblebee = Bumblebee::new(&params, colony.location, colony.species, &mut rng);
let bumblebee =
Bumblebee::new(id, &params, colony.location, colony.species, &mut rng);
id += 1;
if dest == rank {
bumblebees.push(bumblebee);
......
......@@ -69,6 +69,7 @@ The parameter \inlinecode{species} is also associated with each instance and is
\begin{code}
#[derive(Clone, Copy)]
pub struct Insect {
pub id: u32,
pub position: Vector,
pub movement: Vector,
pub species: Species,
......@@ -107,6 +108,7 @@ Finally, the infection status is randomly initialised here using the strategy gi
\begin{code}
impl Insect {
pub fn new(
id: u32,
params: &Params,
insect_params: &InsectParams,
position: Vector,
......@@ -120,6 +122,7 @@ impl Insect {
.status(species, params, insect_params, rng);
Self {
id,
position,
movement: Default::default(),
species,
......@@ -269,6 +272,7 @@ As the scarcity of nectar is primarily used as a driver for the exploration of t
}
infection_contamination(
self.id,
params,
insect_params,
self.species,
......
......@@ -162,7 +162,7 @@ pub struct World {
pub type Contacts = Vec<((FlowerPatchSpecies, InsectSpecies), (usize, Vec<usize>))>;
pub type Vectors = Vec<((FlowerPatchSpecies, InsectSpecies, InsectSpecies), usize)>;
pub type Vectors = Vec<(Tick, u32, u32)>;
\end{code}
\section{Process overview and scheduling}
......
......@@ -173,7 +173,7 @@ impl Observer {
visited_flowers: &mut [Vec<Vec<u16>>; 3],
tick: Tick,
flower_patches: &[FlowerPatch],
flowers: &[u64],
_flowers: &[u64],
hoverflies: &mut Vec<Hoverfly>,
solitary_bees: &mut Vec<SolitaryBee>,
bumblebees: &mut Vec<Bumblebee>,
......@@ -241,7 +241,7 @@ If there are any such insects, the one that is closest to the observer is remove
position: self.position,
flower_patch_species: flower_patch.species,
insect_species: insect.species,
contaminated: flower_patch.contaminated_flowers.count(flowers) != 0,
contaminated: !flower_patch.contaminated_flowers.is_empty(),
infected: insect.pathogen_status.is_infected(),
});
......
......@@ -50,12 +50,9 @@ The contamination of the flowers within a patch is tracked individually, so the
\begin{hiddencode}
use std::collections::BinaryHeap;
use std::mem::MaybeUninit;
use rand_distr::{Bernoulli, Distribution};
use access::dictionary;
use crate::{
bumblebee::Colony,
flower_patch::{Event as FlowerPatchEvent, FlowerPatch},
......@@ -97,39 +94,46 @@ When an insect is infected, the micro-observable \inlinecode{vectors} is updated
\begin{code}
#[allow(clippy::too_many_arguments)]
pub fn infection_contamination(
id: u32,
params: &Params,
insect_params: &InsectParams,
species: InsectSpecies,
_species: InsectSpecies,
status: &mut Status,
index: TaggedIndex,
flower: u16,
flower_patch_events: &mut BinaryHeap<FlowerPatchEvent>,
tick: Tick,
flower_patch: &mut FlowerPatch,
flowers: &mut [u64],
_flowers: &mut [u64],
rng: &mut Rng,
vectors: &mut Vectors,
) {
let visited_flower = flower_patch.visited_flowers.entry(flower).or_default();
visited_flower.retain(|visited_by_id, relevant_until| {
if tick <= *relevant_until {
vectors.push((tick, *visited_by_id, id));
true
} else {
false
}
});
visited_flower.entry(id).insert(tick + Tick::hours(3));
let contaminated_by_id = flower_patch.contaminated_flowers.get(&flower);
let is_susceptible = matches!(status, Status::Susceptible);
let is_infected = status.is_infected();
let is_contaminated = flower_patch
.contaminated_flowers
.get(flowers, flower as usize);
let is_contaminated = contaminated_by_id.is_some();
if is_susceptible && is_contaminated && insect_params.infection.sample(rng) {
let recovers_at = tick + insect_params.recovery(params, rng);
*status = Status::Infected(recovers_at);
*dictionary(
vectors,
(
flower_patch.species,
unsafe { flower_patch.contaminated_by_species.assume_init() },
species,
),
) += 1;
// vectors.push((tick, *contaminated_by_id.unwrap(), id));
}
if is_infected && !is_contaminated && insect_params.contamination.sample(rng) {
......@@ -141,11 +145,7 @@ pub fn infection_contamination(
flower,
});
flower_patch
.contaminated_flowers
.set(flowers, flower as usize, true);
flower_patch.contaminated_by_species = MaybeUninit::new(species);
flower_patch.contaminated_flowers.insert(flower, id);
}
}
\end{code}
......
......@@ -69,7 +69,7 @@ pub enum Activity {
\begin{code}
impl SolitaryBee {
pub fn new(params: &Params, position: Vector, rng: &mut Rng) -> Self {
pub fn new(id: u32, params: &Params, position: Vector, rng: &mut Rng) -> Self {
let species = params.init.solitary_bee_species.sample(rng);
let solitary_bee_params = &params.solitary_bees[species as usize];
......@@ -77,6 +77,7 @@ impl SolitaryBee {
let pollen = solitary_bee_params.pollen_per_brood_cell * rng.gen();
let insect = Insect::new(
id,
params,
&solitary_bee_params.insect,
position,
......