1use rustsim_core::{
12 space::Space,
13 types::{AgentId, EdgeId, NodeId},
14};
15use std::collections::HashMap;
16use thiserror::Error;
17
18pub type LinkId = EdgeId;
20
21#[derive(Debug, Clone, Copy, PartialEq, Error)]
23pub enum LinkGeometryError {
24 #[error("link length must be positive")]
26 InvalidLength,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq)]
31pub struct LinkGeometry {
32 pub length: f64,
34}
35
36impl LinkGeometry {
37 pub fn new(length: f64) -> Result<Self, LinkGeometryError> {
39 if length <= 0.0 {
40 return Err(LinkGeometryError::InvalidLength);
41 }
42 Ok(Self { length })
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Error)]
48pub enum LinkSpaceError {
49 #[error("invalid node index {0}")]
51 InvalidNode(NodeId),
52 #[error("invalid link index {0}")]
54 InvalidLink(LinkId),
55 #[error("agent {0} not found on any link")]
57 AgentNotFound(AgentId),
58 #[error("agent {0} already exists on link {1}")]
60 AgentAlreadyOnLink(AgentId, LinkId),
61}
62
63#[derive(Debug, Clone, Copy)]
64struct AgentOnLink {
65 link_id: LinkId,
66 position: f64,
67}
68
69#[derive(Debug, Clone)]
71struct LinkData<P> {
72 from: NodeId,
73 to: NodeId,
74 geometry: LinkGeometry,
75 properties: P,
76 agents: Vec<AgentId>,
77 exit_blocked: bool,
78}
79
80#[derive(Debug, Clone)]
82pub struct LinkSpace<P = ()> {
83 nodes: Vec<NodeData>,
84 links: Vec<LinkData<P>>,
85 agent_state: HashMap<AgentId, AgentOnLink>,
86}
87
88#[derive(Debug, Clone, Default)]
89struct NodeData {
90 outgoing: Vec<LinkId>,
91 incoming: Vec<LinkId>,
92}
93
94impl<P> LinkSpace<P> {
95 pub fn new() -> Self {
97 Self {
98 nodes: Vec::new(),
99 links: Vec::new(),
100 agent_state: HashMap::new(),
101 }
102 }
103
104 pub fn num_nodes(&self) -> usize {
106 self.nodes.len()
107 }
108
109 pub fn num_links(&self) -> usize {
111 self.links.len()
112 }
113
114 pub fn add_node(&mut self) -> NodeId {
116 let id = self.nodes.len();
117 self.nodes.push(NodeData::default());
118 id
119 }
120
121 pub fn add_link(
123 &mut self,
124 from: NodeId,
125 to: NodeId,
126 geometry: LinkGeometry,
127 properties: P,
128 ) -> Result<LinkId, LinkSpaceError> {
129 if from >= self.nodes.len() {
130 return Err(LinkSpaceError::InvalidNode(from));
131 }
132 if to >= self.nodes.len() {
133 return Err(LinkSpaceError::InvalidNode(to));
134 }
135
136 let link_id = self.links.len();
137 self.links.push(LinkData {
138 from,
139 to,
140 geometry,
141 properties,
142 agents: Vec::new(),
143 exit_blocked: false,
144 });
145 self.nodes[from].outgoing.push(link_id);
146 self.nodes[to].incoming.push(link_id);
147 Ok(link_id)
148 }
149
150 pub fn link_geometry(&self, link_id: LinkId) -> Option<&LinkGeometry> {
152 self.links.get(link_id).map(|l| &l.geometry)
153 }
154
155 pub fn link_geometry_mut(&mut self, link_id: LinkId) -> Option<&mut LinkGeometry> {
157 self.links.get_mut(link_id).map(|l| &mut l.geometry)
158 }
159
160 pub fn link_properties(&self, link_id: LinkId) -> Option<&P> {
162 self.links.get(link_id).map(|l| &l.properties)
163 }
164
165 pub fn link_properties_mut(&mut self, link_id: LinkId) -> Option<&mut P> {
167 self.links.get_mut(link_id).map(|l| &mut l.properties)
168 }
169
170 pub fn link_endpoints(&self, link_id: LinkId) -> Option<(NodeId, NodeId)> {
172 self.links.get(link_id).map(|l| (l.from, l.to))
173 }
174
175 pub fn outgoing_links(&self, node: NodeId) -> &[LinkId] {
177 &self.nodes[node].outgoing
178 }
179
180 pub fn incoming_links(&self, node: NodeId) -> &[LinkId] {
182 &self.nodes[node].incoming
183 }
184
185 pub fn agents_on_link(&self, link_id: LinkId) -> usize {
187 self.links.get(link_id).map_or(0, |l| l.agents.len())
188 }
189
190 pub fn agent_ids_on_link(&self, link_id: LinkId) -> &[AgentId] {
192 self.links
193 .get(link_id)
194 .map_or(&[] as &[AgentId], |l| &l.agents)
195 }
196
197 pub fn link_length(&self, link_id: LinkId) -> Option<f64> {
199 self.link_geometry(link_id).map(|g| g.length)
200 }
201
202 pub fn set_link_exit_blocked(&mut self, link_id: LinkId, blocked: bool) {
204 if let Some(link) = self.links.get_mut(link_id) {
205 link.exit_blocked = blocked;
206 }
207 }
208
209 pub fn link_exit_blocked(&self, link_id: LinkId) -> bool {
211 self.links.get(link_id).is_some_and(|l| l.exit_blocked)
212 }
213
214 pub fn agent_position(&self, id: AgentId) -> Option<(LinkId, f64)> {
216 self.agent_state.get(&id).map(|s| (s.link_id, s.position))
217 }
218
219 pub fn add_agent_to_link(
221 &mut self,
222 id: AgentId,
223 link_id: LinkId,
224 position: f64,
225 ) -> Result<(), LinkSpaceError> {
226 if link_id >= self.links.len() {
227 return Err(LinkSpaceError::InvalidLink(link_id));
228 }
229 if self.agent_state.contains_key(&id) {
230 return Err(LinkSpaceError::AgentAlreadyOnLink(id, link_id));
231 }
232
233 let length = self.links[link_id].geometry.length;
234 let pos = position.clamp(0.0, length);
235
236 self.agent_state.insert(
237 id,
238 AgentOnLink {
239 link_id,
240 position: pos,
241 },
242 );
243
244 let link = &mut self.links[link_id];
245 let insert_idx = link
246 .agents
247 .iter()
248 .position(|&aid| match self.agent_state.get(&aid) {
249 None => true,
250 Some(state) => state.position > pos,
251 })
252 .unwrap_or(link.agents.len());
253 link.agents.insert(insert_idx, id);
254
255 Ok(())
256 }
257
258 pub fn remove_agent(&mut self, id: AgentId) -> Result<(), LinkSpaceError> {
260 let state = self
261 .agent_state
262 .remove(&id)
263 .ok_or(LinkSpaceError::AgentNotFound(id))?;
264 let link = &mut self.links[state.link_id];
265 if let Some(i) = link.agents.iter().position(|&a| a == id) {
266 link.agents.remove(i);
267 }
268 Ok(())
269 }
270
271 pub fn advance_agent(&mut self, id: AgentId, distance: f64) -> Result<f64, LinkSpaceError> {
273 let state = self
274 .agent_state
275 .get(&id)
276 .copied()
277 .ok_or(LinkSpaceError::AgentNotFound(id))?;
278
279 let link = &self.links[state.link_id];
280 let length = link.geometry.length;
281 let my_idx = link.agents.iter().position(|&a| a == id).expect(
282 "invariant: agent present in agent_state must also appear on its link's agent list",
283 );
284
285 let max_pos = if my_idx + 1 < link.agents.len() {
286 let ahead_id = link.agents[my_idx + 1];
287 let ahead_pos = self.agent_state[&ahead_id].position;
288 (ahead_pos - 5.0).max(state.position)
289 } else {
290 length
291 };
292
293 let new_pos = (state.position + distance).min(max_pos).min(length);
294 self.agent_state
295 .get_mut(&id)
296 .expect("invariant: agent existed at function entry and has not been removed")
297 .position = new_pos;
298 Ok(new_pos)
299 }
300
301 pub fn agent_at_link_end(&self, id: AgentId) -> bool {
303 if let Some(state) = self.agent_state.get(&id) {
304 let length = self.links[state.link_id].geometry.length;
305 state.position >= length - 0.01
306 } else {
307 false
308 }
309 }
310
311 pub fn transfer_agent(&mut self, id: AgentId, to_link: LinkId) -> Result<(), LinkSpaceError> {
313 let state = self
314 .agent_state
315 .get(&id)
316 .copied()
317 .ok_or(LinkSpaceError::AgentNotFound(id))?;
318
319 if to_link >= self.links.len() {
320 return Err(LinkSpaceError::InvalidLink(to_link));
321 }
322
323 let current_to = self.links[state.link_id].to;
324 let next_from = self.links[to_link].from;
325
326 if current_to != next_from {
327 return Err(LinkSpaceError::InvalidLink(to_link));
328 }
329
330 let old_link = &mut self.links[state.link_id];
331 if let Some(i) = old_link.agents.iter().position(|&a| a == id) {
332 old_link.agents.remove(i);
333 }
334
335 let entry = self
336 .agent_state
337 .get_mut(&id)
338 .expect("invariant: agent existed at function entry and has not been removed");
339 entry.link_id = to_link;
340 entry.position = 0.0;
341
342 let new_link = &mut self.links[to_link];
343 new_link.agents.insert(0, id);
344
345 Ok(())
346 }
347
348 pub fn total_agents(&self) -> usize {
350 self.agent_state.len()
351 }
352
353 pub fn all_agent_ids(&self) -> Vec<AgentId> {
355 self.agent_state.keys().copied().collect()
356 }
357}
358
359impl<P> Default for LinkSpace<P> {
360 fn default() -> Self {
361 Self::new()
362 }
363}
364
365impl<P: Send + Sync> Space for LinkSpace<P> {}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370
371 fn simple_network() -> LinkSpace<()> {
372 let mut space = LinkSpace::new();
373 let a = space.add_node();
374 let b = space.add_node();
375 let c = space.add_node();
376 space
377 .add_link(a, b, LinkGeometry::new(500.0).unwrap(), ())
378 .unwrap();
379 space
380 .add_link(b, c, LinkGeometry::new(300.0).unwrap(), ())
381 .unwrap();
382 space
383 }
384
385 #[test]
386 fn network_topology() {
387 let space = simple_network();
388 assert_eq!(space.num_nodes(), 3);
389 assert_eq!(space.num_links(), 2);
390 assert_eq!(space.link_endpoints(0), Some((0, 1)));
391 assert_eq!(space.link_endpoints(1), Some((1, 2)));
392 assert_eq!(space.outgoing_links(0), &[0]);
393 assert_eq!(space.incoming_links(1), &[0]);
394 assert_eq!(space.link_length(0), Some(500.0));
395 }
396
397 #[test]
398 fn add_and_remove_agent() {
399 let mut space = simple_network();
400 space.add_agent_to_link(1, 0, 100.0).unwrap();
401
402 assert_eq!(space.agents_on_link(0), 1);
403 assert_eq!(space.agent_position(1), Some((0, 100.0)));
404
405 space.remove_agent(1).unwrap();
406 assert_eq!(space.agents_on_link(0), 0);
407 assert!(space.agent_position(1).is_none());
408 }
409
410 #[test]
411 fn advance_agent_basic() {
412 let mut space = simple_network();
413 space.add_agent_to_link(1, 0, 0.0).unwrap();
414
415 let new_pos = space.advance_agent(1, 50.0).unwrap();
416 assert!((new_pos - 50.0).abs() < 1e-9);
417
418 let new_pos = space.advance_agent(1, 1000.0).unwrap();
419 assert!((new_pos - 500.0).abs() < 1e-9);
420 }
421
422 #[test]
423 fn fifo_ordering() {
424 let mut space = simple_network();
425 space.add_agent_to_link(1, 0, 100.0).unwrap();
426 space.add_agent_to_link(2, 0, 200.0).unwrap();
427
428 let new_pos = space.advance_agent(1, 200.0).unwrap();
429 assert!(new_pos <= 195.0 + 1e-9);
430 }
431
432 #[test]
433 fn transfer_agent() {
434 let mut space = simple_network();
435 space.add_agent_to_link(1, 0, 490.0).unwrap();
436
437 space.advance_agent(1, 100.0).unwrap();
438 assert!(space.agent_at_link_end(1));
439
440 space.transfer_agent(1, 1).unwrap();
441 assert_eq!(space.agent_position(1), Some((1, 0.0)));
442 assert_eq!(space.agents_on_link(0), 0);
443 assert_eq!(space.agents_on_link(1), 1);
444 }
445
446 #[test]
447 fn exit_blocked_flag() {
448 let mut space = simple_network();
449 assert!(!space.link_exit_blocked(0));
450 space.set_link_exit_blocked(0, true);
451 assert!(space.link_exit_blocked(0));
452 }
453
454 #[test]
455 fn agent_already_on_link_error() {
456 let mut space = simple_network();
457 space.add_agent_to_link(1, 0, 0.0).unwrap();
458 let err = space.add_agent_to_link(1, 0, 50.0).unwrap_err();
459 assert_eq!(err, LinkSpaceError::AgentAlreadyOnLink(1, 0));
460 }
461
462 #[test]
463 fn agent_not_found_error() {
464 let mut space = simple_network();
465 let err = space.advance_agent(99, 10.0).unwrap_err();
466 assert_eq!(err, LinkSpaceError::AgentNotFound(99));
467 }
468}