Commit e57da308 authored by Adam Reichold's avatar Adam Reichold
Browse files

Rework the presentation to emphasise that positions and movements are elements of a vector space.

parent 90066671
Pipeline #8451 passed with stage
in 4 minutes and 7 seconds
......@@ -3,7 +3,7 @@ image: git.ufz.de:4567/ecoepi/sick-bees/builder:latest
before_script:
- export RUSTFLAGS="-Ctarget-cpu=broadwell -Crelocation-model=dynamic-no-pic"
- export CARGO_HOME="$CI_PROJECT_DIR/cargo"
- cargo sweep --stamp
- cargo sweep -r -s
cache:
key: "$CI_JOB_NAME"
......@@ -16,7 +16,7 @@ check:
- cargo documents extract
- cargo clippy --all --all-targets -- --deny warnings
- cargo fmt --all -- --check
- cargo sweep --file
- cargo sweep -r -f
build:
script:
......@@ -25,15 +25,16 @@ build:
- cd visualisation
- cargo build --release --target x86_64-pc-windows-gnu
- cd ..
- cargo sweep --file
- cargo sweep -r -f
artifacts:
paths:
- target/x86_64-pc-windows-gnu/release/*.exe
- visualisation/target/x86_64-pc-windows-gnu/release/*.dll
document:
script:
- cargo documents compile
- cargo sweep --file
- cargo sweep -r -f
artifacts:
paths:
- documents/**/*.pdf
......
......@@ -413,9 +413,9 @@ checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20"
[[package]]
name = "proc-macro2"
version = "1.0.21"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36e28516df94f3dd551a587da5357459d9b36d945a7c37c3557928c1c2ff2a2c"
checksum = "51ef7cd2518ead700af67bf9d1a658d90b6037d77110fd9c0445429d0ba1c6c9"
dependencies = [
"unicode-xid",
]
......
......@@ -68,7 +68,7 @@ The purpose of the \href{https://git.ufz.de/ecoepi/sick-bees}{sick-bees} model i
\end{tikzpicture}
\caption{Entities and their interactions}
\caption{Entities with their cardinalities and interactions}
\end{figure}
......@@ -121,16 +121,20 @@ The model considers a landscape of $1 \mathrm{km}^2$ with continuous spatial coo
\appendix
\section{Units of measurement}
\section{Technicalities}
\input{../src/units.tex}
\section{Coordinate system}
\subsection{Coordinate system}
\input{../src/space.tex}
\subsection{Subdivision of landscape}
\input{../src/grid.tex}
\subsection{Units of measurement}
\input{../src/units.tex}
\printbibliography
\end{document}
......@@ -4,8 +4,8 @@ use rand_distr::{Bernoulli, Normal, WeightedIndex};
use experiments::sample;
use model::{
params::{milligrams_per_hour_to_mass_per_tick, Params},
units::{Rate, Tick},
params::Params,
units::{MassRate, Rate, Tick},
};
fn main() {
......@@ -35,6 +35,8 @@ fn main() {
params.init.nests = 0;
params.init.colonies = 0;
params.transect.wait_until = Tick::months(6);
let mut flower_patch = params.flower_patches[0].clone();
flower_patch.flower_density = Normal::new(500., 50.).unwrap();
flower_patch.nectar_production = Normal::new(1_000., 100.).unwrap();
......@@ -42,7 +44,7 @@ fn main() {
params.flower_patches = vec![flower_patch; if more_species { 5 } else { 2 }].into();
let mut hoverfly = params.hoverflies[0].clone();
hoverfly.insect.nectar_consumption = milligrams_per_hour_to_mass_per_tick(params.tick, 75.);
hoverfly.insect.nectar_consumption = MassRate::milligrams_per_hour(75.) * params.tick;
hoverfly.insect.preferences = vec![1; params.flower_patches.len()].into();
params.hoverflies = vec![hoverfly; if more_species { 4 } else { 2 }].into();
......@@ -78,7 +80,7 @@ fn main() {
.unwrap();
if search_time_limited {
params.init.vegetative_cover /= 6.;
params.init.flower_patch_cover /= 6.;
params.init.empty_flower_patches = Bernoulli::new(0.99).unwrap();
params.infection_rate = Rate::per_minute(1. / 20.);
......
......@@ -23,7 +23,7 @@ fn main() {
scale(&mut params.init.nests);
scale(&mut params.init.colonies);
params.init.vegetative_cover *= factor;
params.init.flower_patch_cover *= factor;
let duration = params.transect.wait_until + Tick::days(1);
......
......@@ -93,10 +93,18 @@ where
let start = start.clone();
let senders = (0..size)
.map(|other| senders[(size * other + rank) as usize].take().unwrap())
.map(|other| {
senders[size as usize * other as usize + rank as usize]
.take()
.unwrap()
})
.collect();
let receivers = (0..size)
.map(|other| receivers[(size * rank + other) as usize].take().unwrap())
.map(|other| {
receivers[size as usize * rank as usize + other as usize]
.take()
.unwrap()
})
.collect();
spawn(move || {
......
......@@ -19,6 +19,8 @@ fn main() {
);
});
let all_interactions = interactions.values().sum::<usize>();
let mut flower_patch_species = interactions
.keys()
.map(|(species, _)| *species)
......@@ -45,7 +47,7 @@ fn main() {
.get(&(*flower_patch_species, *insect_species))
.unwrap_or(&0);
print!("{:10} ", interactions);
print!("{:.3} ", *interactions as f64 / all_interactions as f64);
}
println!();
......
......@@ -31,8 +31,8 @@ use kdtree::KDTree;
use crate::{
flower_patch::{FlowerPatch, Location as FlowerPatchLocation},
insect::{Activity as InsectActivity, Insect, Species as InsectSpecies},
params::{rate_to_probability_per_tick, Params},
space::{reduce, shortest_path, Movement, Position},
params::{tick_invariant_probability, Params},
space::{reduce, shortest_path, Vector},
units::Tick,
Interactions, TaggedIndex, Vectors,
};
......@@ -118,15 +118,12 @@ impl Bumblebee {
}
Activity::Other => {
if self.insect.pollen >= bumblebee_params.pollen_per_bout {
let (direction, distance) = shortest_path(
params,
&self.insect.position,
&self.colony_location.position,
);
self.insect.movement = Movement {
dx: insect_params.speed * direction.cos(),
dy: insect_params.speed * direction.sin(),
let (direction, distance) =
shortest_path(params, self.insect.position, self.colony_location.position);
self.insect.movement = Vector {
x: insect_params.speed * direction.cos(),
y: insect_params.speed * direction.sin(),
};
let ticks = (distance / insect_params.speed) as u64;
......@@ -178,13 +175,8 @@ impl Colony {
species,
capacity,
contaminated: false,
infection: rate_to_probability_per_tick(params.tick, params.infection_rate, 1, 1),
contamination: rate_to_probability_per_tick(
params.tick,
params.contamination_rate,
1,
1,
),
infection: tick_invariant_probability(params.tick, params.infection_rate, 1, 1),
contamination: tick_invariant_probability(params.tick, params.contamination_rate, 1, 1),
}
}
......@@ -197,7 +189,7 @@ impl Colony {
#[derive(Clone, Copy)]
pub struct ColonyLocation {
pub position: Position,
pub position: Vector,
pub index: TaggedIndex,
}
\end{code}
\ No newline at end of file
......@@ -14,8 +14,8 @@ use kdtree::Object;
use crate::{
insect::Species as InsectSpecies,
params::{rate_to_probability_per_tick, Params},
space::Position,
params::{tick_invariant_probability, Params},
space::Vector,
units::{Area, Mass, Tick},
TaggedCount, TaggedIndex,
};
......@@ -60,8 +60,8 @@ impl FlowerPatch {
nectar_production,
contaminated_flowers: Default::default(),
contaminated_by_species: MaybeUninit::uninit(),
infection: rate_to_probability_per_tick(params.tick, params.infection_rate, 0, flowers),
contamination: rate_to_probability_per_tick(
infection: tick_invariant_probability(params.tick, params.infection_rate, 0, flowers),
contamination: tick_invariant_probability(
params.tick,
params.contamination_rate,
flowers,
......@@ -84,13 +84,13 @@ impl FlowerPatch {
}
if self.contaminated_flowers.reset() {
self.infection = rate_to_probability_per_tick(
self.infection = tick_invariant_probability(
params.tick,
params.infection_rate,
self.contaminated_flowers.get(),
self.flowers,
);
self.contamination = rate_to_probability_per_tick(
self.contamination = tick_invariant_probability(
params.tick,
params.contamination_rate,
self.flowers - self.contaminated_flowers.get(),
......@@ -105,13 +105,13 @@ pub struct Species(pub u8);
#[derive(Clone, Copy)]
pub struct Location {
pub position: Position,
pub position: Vector,
pub index: TaggedIndex,
pub species: Species,
}
impl Object for Location {
type Point = Position;
type Point = Vector;
fn position(&self) -> &Self::Point {
&self.position
......
\begin{smallcode}
use std::cmp::Ordering;
use crate::{params::Params, space::Position, units::Length};
use crate::{
params::Params,
space::Vector,
units::{Length, Scale},
};
pub struct Grid {
pub rank: u8,
......@@ -10,13 +14,18 @@ pub struct Grid {
pub rows: u8,
pub col: u8,
pub row: u8,
pub x_scale: Scale,
pub y_scale: Scale,
}
impl Grid {
pub fn new(rank: u8, size: u8) -> Self {
pub fn new(params: &Params, rank: u8, size: u8) -> Self {
let (cols, rows) = try_cols_rows(1, 1, size).unwrap();
let (col, row) = rank_to_col_row(rank, rows);
let x_scale = cols as f64 / params.length;
let y_scale = rows as f64 / params.length;
Self {
rank,
size,
......@@ -24,10 +33,12 @@ impl Grid {
rows,
col,
row,
x_scale,
y_scale,
}
}
pub fn is_visible(&self, params: &Params, vision: Length, position: &Position) -> bool {
pub fn is_visible(&self, params: &Params, vision: Length, position: &Vector) -> bool {
let x_min = params.length * (self.col as f64 / self.cols as f64) - vision;
let x_max = params.length * ((self.col + 1) as f64 / self.cols as f64) + vision;
......@@ -54,10 +65,10 @@ impl Grid {
&& !(same_col && same_row)
}
pub fn location(&self, params: &Params, position: &Position) -> u8 {
pub fn location(&self, position: &Vector) -> u8 {
unsafe {
let col = (position.x / (params.length / self.cols as f64)).to_int_unchecked::<u8>();
let row = (position.y / (params.length / self.rows as f64)).to_int_unchecked::<u8>();
let col = (self.x_scale * position.x).to_int_unchecked();
let row = (self.y_scale * position.y).to_int_unchecked();
col_row_to_rank(col, row, self.rows)
}
......
......@@ -35,7 +35,7 @@ use crate::{
flower_patch::{FlowerPatch, Location as FlowerPatchLocation},
insect::{Activity as InsectActivity, Insect, Species as InsectSpecies},
params::{Params, CLUTCH_CAPACITY, MEMORY_CAPACITY},
space::{reduce, Movement, Position, WithinDistance},
space::{reduce, Vector, WithinDistance},
units::Tick,
Interactions, TaggedIndex, Vectors,
};
......@@ -50,7 +50,7 @@ pub struct Hoverfly {
}
impl Hoverfly {
pub fn new(params: &Params, position: Position, rng: &mut Pcg64) -> Self {
pub fn new(params: &Params, position: Vector, rng: &mut Pcg64) -> Self {
let species = params.init.hoverfly_species.sample(rng);
let insect = Insect::new(
......@@ -93,9 +93,9 @@ impl Hoverfly {
if next_turn <= tick {
let (sine, cosine) = insect_params.direction.sample(rng).sin_cos();
self.insect.movement = Movement {
dx: insect_params.speed * cosine,
dy: insect_params.speed * sine,
self.insect.movement = Vector {
x: insect_params.speed * cosine,
y: insect_params.speed * sine,
};
self.activity = Activity::SearchClutch(tick + insect_params.between_turns);
......@@ -206,12 +206,12 @@ impl Clutch {
#[derive(Clone, Copy)]
pub struct ClutchLocation {
pub position: Position,
pub position: Vector,
pub index: TaggedIndex,
}
impl Object for ClutchLocation {
type Point = Position;
type Point = Vector;
fn position(&self) -> &Self::Point {
&self.position
......
......@@ -13,7 +13,7 @@ use crate::{
observer::Observer,
params::Params,
solitary_bee::{Nest, NestLocation, SolitaryBee},
space::{mirror, Position},
space::{mirror, Vector},
units::Length,
Model, TaggedIndex, World,
};
......@@ -32,17 +32,17 @@ impl Model {
let x = Length::meters(xy.sample(rng));
let y = Length::meters(xy.sample(rng));
Position { x, y }
Vector { x, y }
}
};
let grid = Grid::new(rank, size);
let grid = Grid::new(&params, rank, size);
let mut flower_patches = Vec::new();
let mut flower_patch_locations = Vec::new();
let mut flower_patch_indices = vec![0; size as usize];
let flower_patch_area = params.length * params.length * params.init.vegetative_cover
let flower_patch_area = params.length * params.length * params.init.flower_patch_cover
/ params.init.flower_patches as f64;
let max_vision = params
......@@ -68,9 +68,9 @@ impl Model {
}
let position = position(&mut rng);
let dest = grid.location(&params, &position);
let dest = grid.location(&position);
for position in mirror(&params, &position) {
for position in mirror(&params, position) {
if grid.is_visible(&params, max_vision, &position) {
flower_patch_locations.push(FlowerPatchLocation {
position,
......@@ -92,7 +92,7 @@ impl Model {
for _ in 0..params.init.hoverflies {
let hoverfly = Hoverfly::new(&params, position(&mut rng), &mut rng);
let dest = grid.location(&params, &hoverfly.insect.position);
let dest = grid.location(&hoverfly.insect.position);
if dest == rank {
hoverflies.push(hoverfly);
......@@ -108,7 +108,7 @@ impl Model {
.iter()
.map(|hoverfly| hoverfly.vision + hoverfly.insect.speed)
.max_by(|lhs, rhs| lhs.partial_cmp(&rhs).unwrap())
.unwrap();
.unwrap_or_default();
for _ in 0..params.init.clutches {
let clutch = Clutch::new(&params, &mut rng);
......@@ -118,9 +118,9 @@ impl Model {
}
let position = position(&mut rng);
let dest = grid.location(&params, &position);
let dest = grid.location(&position);
for position in mirror(&params, &position) {
for position in mirror(&params, position) {
if grid.is_visible(&params, max_vision, &position) {
clutch_locations.push(ClutchLocation {
position,
......@@ -141,7 +141,7 @@ impl Model {
for _ in 0..params.init.solitary_bees {
let solitary_bee = SolitaryBee::new(&params, position(&mut rng), &mut rng);
let dest = grid.location(&params, &solitary_bee.insect.position);
let dest = grid.location(&solitary_bee.insect.position);
if dest == rank {
solitary_bees.push(solitary_bee);
......@@ -157,15 +157,15 @@ impl Model {
.iter()
.map(|solitary_bee| solitary_bee.vision + solitary_bee.insect.speed)
.max_by(|lhs, rhs| lhs.partial_cmp(&rhs).unwrap())
.unwrap();
.unwrap_or_default();
for _ in 0..params.init.nests {
let nest = Nest::new();
let position = position(&mut rng);
let dest = grid.location(&params, &position);
let dest = grid.location(&position);
for position in mirror(&params, &position) {
for position in mirror(&params, position) {
if grid.is_visible(&params, max_vision, &position) {
nest_locations.push(NestLocation {
position,
......@@ -196,7 +196,7 @@ impl Model {
continue;
}
let dest = grid.location(&params, &colony.location.position);
let dest = grid.location(&colony.location.position);
if dest == rank {
colonies.push(colony);
......@@ -214,7 +214,7 @@ impl Model {
let observer = {
let observer = Observer::new(&params);
let dest = grid.location(&params, &observer.position);
let dest = grid.location(&observer.position);
if dest == rank {
Some(observer)
......
......@@ -34,7 +34,7 @@ use queue::Queue;
use crate::{
flower_patch::{FlowerPatch, Location as FlowerPatchLocation, Nectar},
params::{Insect as InsectParams, Params, MEMORY_CAPACITY},
space::{reduce, Movement, Position, WithinDistance},
space::{reduce, Vector, WithinDistance},
units::{Tick, Volume},
Interactions, TaggedIndex, Vectors,
};
......@@ -43,8 +43,8 @@ use crate::{
\begin{code}
#[derive(Clone, Copy)]
pub struct Insect {
pub position: Position,
pub movement: Movement,
pub position: Vector,
pub movement: Vector,
pub species: Species,
pub activity: Activity,
pub memory: Queue<TaggedIndex, MEMORY_CAPACITY>,
......@@ -53,7 +53,7 @@ pub struct Insect {
}
impl Insect {
pub fn new(params: &Params, position: Position, species: Species, rng: &mut Pcg64) -> Self {
pub fn new(params: &Params, position: Vector, species: Species, rng: &mut Pcg64) -> Self {
let infected = params.init.infected_insects.sample(rng);
Self {
......@@ -85,9 +85,9 @@ impl Insect {
if next_turn <= tick {
let (sine, cosine) = insect_params.direction.sample(rng).sin_cos();
self.movement = Movement {
dx: insect_params.speed * cosine,
dy: insect_params.speed * sine,
self.movement = Vector {
x: insect_params.speed * cosine,
y: insect_params.speed * sine,
};
self.activity = Activity::SearchFlowerPatch(tick + insect_params.between_turns);
......
......@@ -21,12 +21,12 @@
\end{tikzpicture}
\caption{Division of the landscape for distributed simulation by communicating processes}
\caption{Subdivision of landscape for distributed simulation by communicating processes}
\end{figure}
\begin{smallcode}
#![feature(const_fn, partition_point)]
#![feature(const_fn_floating_point_arithmetic, partition_point)]
pub mod bumblebee;
pub mod flower_patch;
......@@ -64,7 +64,7 @@ use crate::{
};
\end{smallcode}
The model is implemented as a distributed simulation using communicating processes which exchange messages afte each time step.
The model is implemented as a distributed simulation using communicating processes which exchange messages after each time step.
\begin{code}
pub struct Model {
......@@ -89,7 +89,7 @@ impl Model {
&self.nest_locations,
);
self.world.fill_outbox(&self.params, &mut self.outbox);
self.world.fill_outbox(&mut self.outbox);
process.exchange(&self.neighbors, &mut self.inbox, &mut self.outbox);
......@@ -200,14 +200,12 @@ impl World {
self.tick += params.tick;
}
pub fn fill_outbox(&mut self, params: &Params, outbox: &mut Vec<Relocation>) {
pub fn fill_outbox(&mut self, outbox: &mut Vec<Relocation>) {
let mut idx = 0;
let mut len = self.hoverflies.len();
while idx != len {
let dest = self
.grid
.location(params, &self.hoverflies[idx].insect.position);
let dest = self.grid.location(&self.hoverflies[idx].insect.position);
if dest != self.grid.rank {
outbox.push(Relocation::Hoverfly(dest, self.hoverflies.swap_remove(idx)));
......@@ -222,9 +220,7 @@ impl World {
len = self.solitary_bees.len();
while idx != len {
let dest = self
.grid
.location(params, &self.solitary_bees[idx].insect.position);
let dest = self.grid.location(&self.solitary_bees[idx].insect.position);
if dest != self.grid.rank {
outbox.push(Relocation::SolitaryBee(
......@@ -242,9 +238,7 @@ impl World {
len = self.bumblebees.len();
while idx != len {
let dest = self
.grid
.location(params, &self.bumblebees[idx].insect.position);
let dest = self.grid.location(&self.bumblebees[idx].insect.position);
if dest != self.grid.rank {
outbox.push(Relocation::Bumblebee(
......@@ -259,7 +253,7 @@ impl World {
}
if let Some(observer) = &self.observer {
let dest = self.grid.location(params, &observer.position);
let dest = self.grid.location(&observer.position);
if dest != self.grid.rank {
outbox.push(Relocation::Observer(dest, self.observer.take().unwrap()));
......
......@@ -31,7 +31,7 @@ use crate::{
insect::{Activity as InsectActivity, Species as InsectSpecies},
params::Params,
solitary_bee::SolitaryBee,
space::Position,
space::Vector,
units::Tick,
};
\end{smallcode}
......@@ -40,7 +40,7 @@ use crate::{
#[derive(Clone, Copy)]
pub struct Observer {
pub duration: Tick,
pub position: Position,
pub position: Vector,
pub activity: Activity,
}
......@@ -49,7 +49,7 @@ impl Observer {
let radius = -params.transect.length / 2.;
let (sine, cosine) = params.transect.direction.sin_cos();
let position = Position {
let position = Vector {
x: params.length / 2. + radius * cosine,
y: params.length / 2. + radius * sine,
};
......@@ -104,7 +104,7 @@ impl Observer {
matches!(insect.activity, InsectActivity::CollectNectarAndPollen(..))
})
.map(|(idx, insect)| {
let distance_2 = insect.position.distance_2(&self.position);
let distance_2 = (insect.position - self.position).norm_2();
<