use crate::{
agent::Agent, scheduler::Scheduler, space::Space, standard::StandardModel, store::AgentStore,
types::AgentId,
};
use rand::seq::SliceRandom;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum InteractionError<E: std::fmt::Debug + std::fmt::Display> {
#[error("duplicate agent id {0}")]
DuplicateId(AgentId),
#[error("agent not found: {0}")]
AgentNotFound(AgentId),
#[error("agent {0} is missing from the spatial index at its stored position")]
SpaceIndexMissing(AgentId),
#[error("agent {0} appears more than once in the spatial index at its stored position")]
SpaceIndexDuplicate(AgentId),
#[error("space error: {0}")]
Space(E),
#[error("space rollback failed during {operation}: original error: {source}; rollback error: {rollback}")]
RollbackFailed {
operation: &'static str,
source: E,
rollback: E,
},
}
pub trait PositionedAgent: Agent {
type Position: Clone;
fn position(&self) -> &Self::Position;
fn set_position(&mut self, position: Self::Position);
}
pub trait SpaceInteraction<A: PositionedAgent>: Space {
type Error: std::fmt::Debug + std::fmt::Display;
fn random_position<R: rand::RngCore>(&self, rng: &mut R) -> A::Position;
fn add_agent(&mut self, agent: &A) -> Result<(), Self::Error>;
fn remove_agent(&mut self, agent: &A) -> Result<(), Self::Error>;
fn nearby_ids(&self, position: &A::Position, radius: usize) -> Vec<AgentId>;
}
pub fn add_agent<S, A, Store, Props, R, Sch>(
model: &mut StandardModel<S, A, Store, Props, R, Sch>,
agent: A,
) -> Result<(), InteractionError<S::Error>>
where
A: PositionedAgent,
S: SpaceInteraction<A>,
Store: AgentStore<A>,
R: rand::RngCore,
Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
{
let id = agent.id();
if model.agents.contains(id) {
return Err(InteractionError::DuplicateId(id));
}
model
.space
.add_agent(&agent)
.map_err(InteractionError::Space)?;
model.agents.insert(agent);
if id > model.max_id {
model.max_id = id;
}
Ok(())
}
pub fn validate_space_index<S, A, Store, Props, R, Sch>(
model: &StandardModel<S, A, Store, Props, R, Sch>,
) -> Result<(), InteractionError<S::Error>>
where
A: PositionedAgent,
S: SpaceInteraction<A>,
Store: AgentStore<A>,
R: rand::RngCore,
Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
{
for id in model.agents.iter_ids() {
let Some(agent) = model.agents.get(id) else {
continue;
};
let matches = model
.space
.nearby_ids(agent.position(), 0)
.into_iter()
.filter(|candidate| *candidate == id)
.count();
match matches {
0 => return Err(InteractionError::SpaceIndexMissing(id)),
1 => {}
_ => return Err(InteractionError::SpaceIndexDuplicate(id)),
}
}
Ok(())
}
pub fn remove_agent<S, A, Store, Props, R, Sch>(
model: &mut StandardModel<S, A, Store, Props, R, Sch>,
id: AgentId,
) -> Result<Option<A>, InteractionError<S::Error>>
where
A: PositionedAgent,
S: SpaceInteraction<A>,
Store: AgentStore<A>,
R: rand::RngCore,
Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
{
if let Some(agent_ref) = model.agents.get(id) {
model
.space
.remove_agent(&*agent_ref)
.map_err(InteractionError::Space)?;
} else {
return Ok(None);
}
Ok(model.agents.remove(id))
}
pub fn move_agent<S, A, Store, Props, R, Sch>(
model: &mut StandardModel<S, A, Store, Props, R, Sch>,
id: AgentId,
new_position: A::Position,
) -> Result<(), InteractionError<S::Error>>
where
A: PositionedAgent,
S: SpaceInteraction<A>,
Store: AgentStore<A>,
R: rand::RngCore,
Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
{
let mut agent_ref = model
.agents
.get_mut(id)
.ok_or(InteractionError::AgentNotFound(id))?;
let old_position = agent_ref.position().clone();
model
.space
.remove_agent(&*agent_ref)
.map_err(InteractionError::Space)?;
agent_ref.set_position(new_position);
if let Err(source) = model.space.add_agent(&*agent_ref) {
agent_ref.set_position(old_position);
if let Err(rollback) = model.space.add_agent(&*agent_ref) {
return Err(InteractionError::RollbackFailed {
operation: "move_agent",
source,
rollback,
});
}
return Err(InteractionError::Space(source));
}
Ok(())
}
pub fn random_id<S, A, Store, Props, R, Sch>(
model: &mut StandardModel<S, A, Store, Props, R, Sch>,
) -> Option<AgentId>
where
A: Agent,
S: Space,
Store: AgentStore<A>,
R: rand::RngCore,
Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
{
let ids: Vec<AgentId> = model.agents.iter_ids();
if ids.is_empty() {
return None;
}
let mut rng = model.rng_mut();
ids.choose(&mut *rng).copied()
}
pub fn all_ids<S, A, Store, Props, R, Sch>(
model: &StandardModel<S, A, Store, Props, R, Sch>,
) -> Vec<AgentId>
where
A: Agent,
S: Space,
Store: AgentStore<A>,
R: rand::RngCore,
Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
{
model.agents.iter_ids()
}
pub fn nearby_ids<S, A, Store, Props, R, Sch>(
model: &StandardModel<S, A, Store, Props, R, Sch>,
position: &A::Position,
radius: usize,
) -> Vec<AgentId>
where
A: PositionedAgent,
S: SpaceInteraction<A>,
Store: AgentStore<A>,
R: rand::RngCore,
Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
{
model.space.nearby_ids(position, radius)
}
pub fn nearby_agents<'a, S, A, Store, Props, R, Sch>(
model: &'a StandardModel<S, A, Store, Props, R, Sch>,
position: &A::Position,
radius: usize,
) -> Vec<std::cell::Ref<'a, A>>
where
A: PositionedAgent,
S: SpaceInteraction<A>,
Store: AgentStore<A>,
R: rand::RngCore,
Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
{
model
.space
.nearby_ids(position, radius)
.into_iter()
.filter_map(|id| model.agents.get(id))
.collect()
}
pub fn nearby_ids_except<S, A, Store, Props, R, Sch>(
model: &StandardModel<S, A, Store, Props, R, Sch>,
position: &A::Position,
radius: usize,
exclude_id: AgentId,
) -> Vec<AgentId>
where
A: PositionedAgent,
S: SpaceInteraction<A>,
Store: AgentStore<A>,
R: rand::RngCore,
Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
{
model
.space
.nearby_ids(position, radius)
.into_iter()
.filter(|&id| id != exclude_id)
.collect()
}
pub fn nearby_agents_except<'a, S, A, Store, Props, R, Sch>(
model: &'a StandardModel<S, A, Store, Props, R, Sch>,
position: &A::Position,
radius: usize,
exclude_id: AgentId,
) -> Vec<std::cell::Ref<'a, A>>
where
A: PositionedAgent,
S: SpaceInteraction<A>,
Store: AgentStore<A>,
R: rand::RngCore,
Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
{
model
.space
.nearby_ids(position, radius)
.into_iter()
.filter(|&id| id != exclude_id)
.filter_map(|id| model.agents.get(id))
.collect()
}
pub fn random_agent<'a, S, A, Store, Props, R, Sch>(
model: &'a mut StandardModel<S, A, Store, Props, R, Sch>,
) -> Option<std::cell::Ref<'a, A>>
where
A: Agent,
S: Space,
Store: AgentStore<A>,
R: rand::RngCore,
Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
{
let ids = model.agents.iter_ids();
if ids.is_empty() {
return None;
}
let mut rng = model.rng_mut();
let id = *ids.choose(&mut *rng)?;
drop(rng);
model.agents.get(id)
}
pub fn add_agent_random<S, A, Store, Props, R, Sch>(
model: &mut StandardModel<S, A, Store, Props, R, Sch>,
mut agent: A,
) -> Result<(), InteractionError<S::Error>>
where
A: PositionedAgent,
S: SpaceInteraction<A>,
Store: AgentStore<A>,
R: rand::RngCore,
Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
{
let mut rng = model.rng_mut();
let position = model.space.random_position(&mut *rng);
drop(rng);
agent.set_position(position);
add_agent(model, agent)
}