use crate::behaviour::BehaviourType;
use crate::agents::{VehicleID, Vehicle};
use crate::conflict_zones::{ConflictWinnerType, ConflictZone, ConflictZoneID};
use crate::conflicts::resolve_simple_rules;
use crate::grid::cell::{Cell, CellID};
use crate::grid::road_network::GridRoads;
use crate::maneuver::LaneChangeType;
use crate::intentions::{CellIntention, IntentionType, Intentions};
use crate::utils::rand::rng;
use crate::verbose::{LocalLogger, VerboseLevel};
use rand::Rng;
use std::collections::{HashMap, HashSet};
use indexmap::IndexMap;
use std::{fmt, vec};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConflictType {
Undefined,
ForwardLaneChange,
BlockLaneChange,
MergeForward,
MergeLaneChange,
MergeForwardConflictZone,
CrossLaneChange,
CrossConflictZone,
Tail,
SelfTail,
TailCrossLaneChange,
}
impl fmt::Display for ConflictType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let as_str = match self {
ConflictType::Undefined => "undefined",
ConflictType::ForwardLaneChange => "forward+lane_change",
ConflictType::BlockLaneChange => "block+lane_change",
ConflictType::MergeForward => "merge+forward",
ConflictType::MergeLaneChange => "merge+lane_change",
ConflictType::MergeForwardConflictZone => "merge+forward+conflict_zone",
ConflictType::CrossLaneChange => "cross+lane_change",
ConflictType::CrossConflictZone => "cross+conflict_zone",
ConflictType::Tail => "tail",
ConflictType::SelfTail => "self_tail",
ConflictType::TailCrossLaneChange => "tail+cross+lane_change",
};
write!(f, "{}", as_str)
}
}
#[derive(Debug)]
pub struct CellConflict {
pub cell_id: CellID,
pub participants: Vec<VehicleID>,
pub priority_participant_index: usize,
pub conflict_type: ConflictType,
}
impl fmt::Display for CellConflict {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let participants: Vec<String> =
self.participants.iter().map(|p| p.to_string()).collect();
let priority_participant_id = self.participants
.get(self.priority_participant_index)
.map_or("None".to_string(), |p| p.to_string());
let cell_id = self.cell_id.to_string();
write!(
f,
"CellConflict{{CellID: {}, Type: {}, Participants: ({:?}), Priority participant: {}}}",
cell_id, self.conflict_type, participants, priority_participant_id
)
}
}
#[derive(Debug, Clone)]
pub enum ConflictError {
CellNotFound(CellID),
InvalidVehicle(String),
}
impl std::fmt::Display for ConflictError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::CellNotFound(cell_id) => write!(f, "Cell {} not found in network", cell_id),
Self::InvalidVehicle(msg) => write!(f, "Invalid vehicle: {}", msg),
}
}
}
pub struct TrajectoryConflictInfo {
pub vehicle_id: u64,
pub side_vehicle_id: u64,
pub conflict_type: ConflictType,
pub priority_vehicle_id: u64,
}
pub fn find_cross_trajectories_conflict_naive(
cell_intention: &CellIntention,
intention_cell: &Cell,
collected_intentions: &Intentions,
net: &GridRoads,
vehicles: &indexmap::IndexMap<VehicleID, Vehicle>,
) -> Result<Option<TrajectoryConflictInfo>, ConflictError> {
let vehicle = vehicles.get(&cell_intention.get_vehicle_id()).ok_or(
ConflictError::InvalidVehicle(format!("Vehicle {} not found", cell_intention.get_vehicle_id()))
)?;
let mut cell_x = vehicle.cell_id;
let mut cell_b = intention_cell.get_id();
let mut conflict_type = ConflictType::CrossLaneChange;
if !vehicle.tail_cells.is_empty() &&
vehicle.intention.intention_maneuver != LaneChangeType::ChangeRight &&
vehicle.intention.tail_maneuver.intention_maneuver != LaneChangeType::ChangeRight {
return Ok(None);
}
if !vehicle.tail_cells.is_empty() && vehicle.intention.tail_maneuver.intention_maneuver == LaneChangeType::ChangeRight {
cell_x = vehicle.intention.tail_maneuver.source_cell_maneuver;
cell_b = vehicle.intention.tail_maneuver.target_cell_maneuver;
conflict_type = ConflictType::TailCrossLaneChange;
}
if vehicle.tail_cells.is_empty() && vehicle.intention.intention_maneuver != LaneChangeType::ChangeRight {
return Ok(None);
}
let current_cell = net.get_cell(&cell_x)
.ok_or(ConflictError::CellNotFound(cell_x))?;
if current_cell.get_forward_id() < 0 {
return Ok(None);
}
let forward_intentions = collected_intentions.get(¤t_cell.get_forward_id());
if forward_intentions.is_none() || forward_intentions.unwrap().is_empty() {
return Ok(None);
}
let mut side_vehicle_left_id: Option<VehicleID> = None;
let mut last_cell_intention = IntentionType::Target;
for forward_intention in forward_intentions.unwrap() {
let fwd_vehicle = vehicles.get(&forward_intention.get_vehicle_id()).ok_or(
ConflictError::InvalidVehicle(format!("Vehicle {} not found", forward_intention.get_vehicle_id()))
)?;
if fwd_vehicle.intention.intention_maneuver == LaneChangeType::ChangeLeft || forward_intention.int_type == IntentionType::Tail {
last_cell_intention = forward_intention.int_type;
side_vehicle_left_id = Some(forward_intention.get_vehicle_id());
break;
}
}
let side_vehicle_id = match side_vehicle_left_id {
Some(vehicle_id) => vehicle_id,
None => return Ok(None), };
let side_vehicle = vehicles.get(&side_vehicle_id).ok_or(
ConflictError::InvalidVehicle(format!("Vehicle {} not found", side_vehicle_id))
)?;
if last_cell_intention == IntentionType::Tail {
if side_vehicle.tail_cells.is_empty() {
return Err(ConflictError::InvalidVehicle(
format!("Cell intention with 'INTENTION_TAIL' type has reference to vehicle which has no tail {}", side_vehicle.id)
));
}
for &occ_cell_id in &side_vehicle.tail_cells {
if occ_cell_id < 1 {
continue;
}
let side_vehicle_cell = net.get_cell(&occ_cell_id)
.ok_or(ConflictError::CellNotFound(occ_cell_id))?;
if side_vehicle_cell.get_forward_id() < 0 {
continue;
}
if side_vehicle_cell.get_forward_id() == cell_b {
return Ok(Some(TrajectoryConflictInfo {
vehicle_id: vehicle.id,
side_vehicle_id: side_vehicle.id,
conflict_type: ConflictType::TailCrossLaneChange,
priority_vehicle_id: side_vehicle.id, }));
}
}
return Ok(None);
}
let side_vehicle_cell = net.get_cell(&side_vehicle.cell_id)
.ok_or(ConflictError::CellNotFound(side_vehicle.cell_id))?;
if side_vehicle_cell.get_forward_id() < 0 {
return Ok(None);
}
if side_vehicle_cell.get_forward_id() == cell_b {
let priority_vehicle_id = if conflict_type == ConflictType::TailCrossLaneChange {
vehicle.id
} else {
if side_vehicle.strategy_type != vehicle.strategy_type &&
vehicle.strategy_type == BehaviourType::Aggressive {
vehicle.id
} else {
side_vehicle.id
}
};
return Ok(Some(TrajectoryConflictInfo {
vehicle_id: vehicle.id,
side_vehicle_id: side_vehicle.id,
conflict_type,
priority_vehicle_id,
}));
}
Ok(None)
}
impl CellConflict {
pub fn from_trajectory_conflict_info(
info: TrajectoryConflictInfo,
vehicle_id: VehicleID,
side_vehicle_id: VehicleID,
) -> Self {
let participants = if info.priority_vehicle_id == vehicle_id {
vec![vehicle_id, side_vehicle_id] } else {
vec![side_vehicle_id, vehicle_id] };
let priority_index = 0;
CellConflict {
cell_id: -1, participants,
priority_participant_index: priority_index,
conflict_type: info.conflict_type,
}
}
}
pub fn find_zone_conflict_for_two_intentions(
intention_cell_id: CellID,
conflict_zones: &HashMap<ConflictZoneID, ConflictZone>,
cells_conflicts_zones: &HashMap<CellID, ConflictZoneID>,
) -> Option<CellID> {
let conflict_zone_id = cells_conflicts_zones.get(&intention_cell_id)?;
let conflict_zone = conflict_zones.get(conflict_zone_id)?;
let first_edge = conflict_zone.get_first_edge();
let second_edge = conflict_zone.get_second_edge();
if first_edge.source != second_edge.source
&& first_edge.target == second_edge.target
&& first_edge.target == intention_cell_id
{
match conflict_zone.get_winner_type() {
ConflictWinnerType::First => return Some(first_edge.source),
ConflictWinnerType::Second => return Some(second_edge.source),
_ => {}
}
let mut rng = rng();
if rng.random_bool(0.5) {
return Some(first_edge.source);
}
return Some(second_edge.source);
}
None }
pub fn find_conflicts_in_conflict_zones(
cell_intention: &CellIntention,
intention_cell: &Cell,
collected_intentions: &Intentions,
conflict_zones: &HashMap<ConflictZoneID, ConflictZone>,
cells_conflicts_zones: &HashMap<CellID, ConflictZoneID>,
explored_conflict_zones: &mut HashSet<ConflictZoneID>,
vehicles: &indexmap::IndexMap<VehicleID, Vehicle>,
) -> Result<Option<(CellConflict, ConflictZoneID)>, ConflictError> {
let vehicle_id = cell_intention.get_vehicle_id();
let cell_b = intention_cell.get_id();
let conflict_type = ConflictType::CrossConflictZone;
let cell_b_conflict_zone_id = match cells_conflicts_zones.get(&cell_b) {
Some(zone_id) => *zone_id,
None => return Ok(None), };
if explored_conflict_zones.contains(&cell_b_conflict_zone_id) {
return Ok(None); }
let conflict_zone = conflict_zones.get(&cell_b_conflict_zone_id)
.ok_or(ConflictError::InvalidVehicle(
format!("Conflict zone {} not found", cell_b_conflict_zone_id)
))?;
let first_edge = conflict_zone.get_first_edge();
let second_edge = conflict_zone.get_second_edge();
let (cell_a, cell_x) = if first_edge.target == cell_b && second_edge.target != cell_b {
(second_edge.target, second_edge.source)
} else if first_edge.target != cell_b && second_edge.target == cell_b {
(first_edge.target, first_edge.source)
} else {
return Ok(None); };
let cell_a_intentions = match collected_intentions.get(&cell_a) {
Some(intentions) => intentions,
None => return Ok(None), };
let mut second_cell_intention: Option<&CellIntention> = None;
for neighbor_intention in cell_a_intentions {
let neighbor_vehicle = vehicles.get(&neighbor_intention.get_vehicle_id()).ok_or(
ConflictError::InvalidVehicle(format!("Vehicle {} not found", neighbor_intention.get_vehicle_id()))
)?;
if neighbor_vehicle.cell_id == cell_x && !neighbor_vehicle.is_conflict_participant {
second_cell_intention = Some(neighbor_intention);
break;
}
}
let second_cell_intention = match second_cell_intention {
Some(intention) => intention,
None => return Ok(None), };
let second_vehicle = vehicles.get(&second_cell_intention.get_vehicle_id()).ok_or(
ConflictError::InvalidVehicle(format!("Vehicle {} not found", second_cell_intention.get_vehicle_id()))
)?;
if second_vehicle.is_conflict_participant {
return Ok(None); }
let (participants, priority_index) = if second_cell_intention.int_type == IntentionType::Tail
&& cell_intention.int_type != IntentionType::Tail {
(vec![second_cell_intention.get_vehicle_id(), vehicle_id], 0)
} else if second_cell_intention.int_type != IntentionType::Tail
&& cell_intention.int_type == IntentionType::Tail {
(vec![vehicle_id, second_cell_intention.get_vehicle_id()], 0)
} else if second_cell_intention.int_type == IntentionType::Tail
&& cell_intention.int_type == IntentionType::Tail {
return Err(ConflictError::InvalidVehicle(
format!("Incorrect cell intentions for tails. first: {:?}, second: {:?}",
cell_intention, second_cell_intention)
));
} else {
match conflict_zone.get_winner_type() {
ConflictWinnerType::First => {
if first_edge.target == cell_b {
(vec![vehicle_id, second_cell_intention.get_vehicle_id()], 0)
} else {
(vec![second_cell_intention.get_vehicle_id(), vehicle_id], 0)
}
},
ConflictWinnerType::Second => {
if first_edge.target == cell_b {
(vec![second_cell_intention.get_vehicle_id(), vehicle_id], 0)
} else {
(vec![vehicle_id, second_cell_intention.get_vehicle_id()], 0)
}
},
_ => {
use rand::Rng;
let mut rng = rng();
if rng.random_bool(0.5) {
(vec![vehicle_id, second_cell_intention.get_vehicle_id()], 0)
} else {
(vec![second_cell_intention.get_vehicle_id(), vehicle_id], 0)
}
}
}
};
let conflict = CellConflict {
cell_id: -1, participants,
priority_participant_index: priority_index,
conflict_type,
};
Ok(Some((conflict, cell_b_conflict_zone_id)))
}
pub fn find_conflict_type<'a>(
intention_cell_id: CellID,
conflict_zones: &HashMap<ConflictZoneID, ConflictZone>,
cells_conflicts_zones: &HashMap<CellID, ConflictZoneID>,
intention_one: &'a CellIntention,
intention_two: &'a CellIntention,
vehicles: &indexmap::IndexMap<VehicleID, Vehicle>,
) -> (&'a CellIntention, ConflictType) {
let intention_type_one = intention_one.int_type;
let intention_type_two = intention_two.int_type;
if intention_type_one == IntentionType::Tail && intention_type_two == IntentionType::Transit {
return (intention_one, ConflictType::Tail);
}
if intention_type_one == IntentionType::Transit && intention_type_two == IntentionType::Tail {
return (intention_two, ConflictType::Tail);
}
if intention_type_one == IntentionType::Tail {
return (intention_one, ConflictType::Tail);
}
if intention_type_two == IntentionType::Tail {
return (intention_two, ConflictType::Tail);
}
let vehicle_one = vehicles.get(&intention_one.get_vehicle_id()).unwrap();
let vehicle_two = vehicles.get(&intention_two.get_vehicle_id()).unwrap();
let conflict_zone_winner_source_cell = find_zone_conflict_for_two_intentions(
intention_cell_id,
conflict_zones,
cells_conflicts_zones
);
if let Some(winner_cell) = conflict_zone_winner_source_cell {
if vehicle_one.cell_id == winner_cell {
return (intention_one, ConflictType::MergeForwardConflictZone);
}
if vehicle_two.cell_id == winner_cell {
return (intention_two, ConflictType::MergeForwardConflictZone);
}
if !vehicle_one.intention.intermediate_cells.is_empty() {
if let Some(elem_idx) = find_elem_in_slice(winner_cell, &vehicle_one.intention.intermediate_cells) {
let trim_inter = &vehicle_one.intention.intermediate_cells[..=elem_idx];
if let Some(&last_cell_id) = trim_inter.last() {
if last_cell_id == winner_cell {
return (intention_one, ConflictType::MergeForwardConflictZone);
}
}
}
}
if !vehicle_two.intention.intermediate_cells.is_empty() {
if let Some(elem_idx) = find_elem_in_slice(winner_cell, &vehicle_two.intention.intermediate_cells) {
let trim_inter = &vehicle_two.intention.intermediate_cells[..=elem_idx];
if let Some(&last_cell_id) = trim_inter.last() {
if last_cell_id == winner_cell {
return (intention_two, ConflictType::MergeForwardConflictZone);
}
}
}
}
eprintln!(
"Can't happen in CONFLICT_TYPE_MERGE_CONFLICT_ZONE: winner_cell={}, vehicle_one={}, vehicle_two={}, pos_one={}, pos_two={}",
winner_cell, vehicle_one.id, vehicle_two.id, vehicle_one.cell_id, vehicle_two.cell_id
);
panic!("Can't happen in CONFLICT_TYPE_MERGE_CONFLICT_ZONE");
}
resolve_simple_rules(intention_one, intention_two, vehicles)
}
fn find_elem_in_slice(target: CellID, slice: &[CellID]) -> Option<usize> {
slice.iter().position(|&x| x == target)
}
pub fn new_conflict_multiple(
cell: &Cell,
conflict_zones: &HashMap<ConflictZoneID, ConflictZone>,
cells_conflicts_zones: &HashMap<CellID, ConflictZoneID>,
cell_intentions: &[CellIntention],
vehicles: &indexmap::IndexMap<VehicleID, Vehicle>,
) -> Result<CellConflict, ConflictError> {
if cell_intentions.len() < 2 {
return Err(ConflictError::InvalidVehicle(
"Number of cell intentions is less than 2".to_string()
));
}
let mut participants_map: IndexMap<VehicleID, VehicleID> = IndexMap::new();
for intention in cell_intentions {
let vehicle_id = intention.get_vehicle_id();
participants_map.entry(vehicle_id).or_insert(vehicle_id);
}
if participants_map.len() < 2 {
return Ok(CellConflict {
cell_id: -1,
participants: vec![],
priority_participant_index: 0,
conflict_type: ConflictType::SelfTail,
});
}
let participants: Vec<VehicleID> = participants_map.values().cloned().collect();
let mut conflict = CellConflict {
cell_id: cell.get_id(),
participants,
priority_participant_index: 0, conflict_type: ConflictType::Undefined,
};
let mut priority_intention = &cell_intentions[0];
let mut conflict_type = ConflictType::Undefined;
for i in 1..cell_intentions.len() {
let (winner, c_type) = find_conflict_type(
cell.get_id(),
conflict_zones,
cells_conflicts_zones,
priority_intention,
&cell_intentions[i],
vehicles,
);
priority_intention = winner;
conflict_type = c_type;
}
let priority_vehicle_id = priority_intention.get_vehicle_id();
let mut priority_index = 0;
for (i, participant) in conflict.participants.iter().enumerate() {
if *participant == priority_vehicle_id {
priority_index = i;
break;
}
}
conflict.priority_participant_index = priority_index;
conflict.conflict_type = conflict_type;
Ok(conflict)
}
pub fn collect_conflicts(
collected_intentions: &Intentions,
net: &GridRoads,
conflict_zones: &HashMap<ConflictZoneID, ConflictZone>,
cells_conflicts_zones: &HashMap<CellID, ConflictZoneID>,
verbose: &LocalLogger,
vehicles: &mut indexmap::IndexMap<VehicleID, Vehicle>,
) -> Result<Vec<CellConflict>, ConflictError> {
if verbose.is_at_least(crate::verbose::VerboseLevel::Main) {
verbose.log_with_fields(
crate::verbose::EVENT_CONFLICTS_COLLECT,
"Collect conflicts",
&[
("intentions_num", &collected_intentions.len()),
("conflict_zones_num", &conflict_zones.len()),
("matched_cells_zones_num", &cells_conflicts_zones.len()),
]
);
}
let mut conflicts_data = Vec::with_capacity(collected_intentions.len() / 2); let mut explored_conflict_zones: HashSet<ConflictZoneID> = HashSet::new();
for (intention_cell_id, cell_intentions) in collected_intentions.iter() {
if cell_intentions.is_empty() {
return Err(ConflictError::InvalidVehicle(
"This should not happen - empty cell intentions".to_string()
));
}
let intention_cell = net.get_cell(intention_cell_id)
.ok_or(ConflictError::CellNotFound(*intention_cell_id))?;
let vehicles_num = cell_intentions.len();
if vehicles_num == 1 {
if verbose.is_at_least(crate::verbose::VerboseLevel::Additional) {
verbose.log_with_fields(
crate::verbose::EVENT_CONFLICTS_COLLECT,
"Inspect single-vehicle intention",
&[
("cell_intention", &format!("{:?}", cell_intentions[0])),
("intention_cell", &intention_cell.get_id()),
]
);
}
let cell_intention = &cell_intentions[0];
let should_skip = {
let vehicle = vehicles.get(&cell_intention.get_vehicle_id()).ok_or(
ConflictError::InvalidVehicle(format!("Vehicle {} not found", cell_intention.get_vehicle_id()))
)?;
vehicle.intention.intention_maneuver == LaneChangeType::Block || vehicle.is_conflict_participant
};
if should_skip {
if verbose.is_at_least(VerboseLevel::Additional) {
let vehicle = vehicles.get(&cell_intention.get_vehicle_id()).unwrap();
verbose.log_with_fields(
crate::verbose::EVENT_CONFLICTS_COLLECT,
"Vehicle is blocked or is in conflict already",
&[
("vehicle_id", &vehicle.id),
("maneuver", &format!("{:?}", vehicle.intention.intention_maneuver)),
("is_conflict_participant", &vehicle.is_conflict_participant),
("intention_cell", &intention_cell.get_id()),
]
);
}
continue;
}
let mut possible_cross_conflict: Option<CellConflict> = None;
if verbose.is_at_least(VerboseLevel::Additional) {
verbose.log_with_fields(
crate::verbose::EVENT_CONFLICTS_COLLECT,
"Try to find simple trajectories conflict",
&[
("cell_intention", &format!("{:?}", cell_intention)),
("intention_cell", &intention_cell.get_id()),
]
);
}
let cross_conflict_info = find_cross_trajectories_conflict_naive(
cell_intention, intention_cell, collected_intentions, net, vehicles
)?;
if verbose.is_at_least(VerboseLevel::Additional) {
verbose.log_with_fields(
crate::verbose::EVENT_CONFLICTS_COLLECT,
"After simple trajectories scan",
&[
("cell_intention", &format!("{:?}", cell_intention)),
("intention_cell", &intention_cell.get_id()),
("is_trajectory_conflict", &cross_conflict_info.is_some()),
]
);
}
if let Some(conflict_info) = cross_conflict_info {
let side_id = conflict_info.side_vehicle_id;
let conflict = CellConflict::from_trajectory_conflict_info(
conflict_info,
cell_intention.get_vehicle_id(),
side_id,
);
possible_cross_conflict = Some(conflict);
}
if possible_cross_conflict.is_none() {
if verbose.is_at_least(VerboseLevel::Additional) {
verbose.log_with_fields(
crate::verbose::EVENT_CONFLICTS_COLLECT,
"Try to find conflict zones trajectories conflict",
&[
("cell_intention", &format!("{:?}", cell_intention)),
("intention_cell", &intention_cell.get_id()),
]
);
}
let zone_conflict_result = find_conflicts_in_conflict_zones(
cell_intention,
intention_cell,
collected_intentions,
conflict_zones,
cells_conflicts_zones,
&mut explored_conflict_zones,
vehicles,
)?;
if verbose.is_at_least(VerboseLevel::Additional) {
verbose.log_with_fields(
crate::verbose::EVENT_CONFLICTS_COLLECT,
"After conflict zones trajectories scan",
&[
("cell_intention", &format!("{:?}", cell_intention)),
("intention_cell", &intention_cell.get_id()),
("is_trajectory_conflict", &zone_conflict_result.is_some()),
]
);
}
if let Some((conflict, conflict_zone_id)) = zone_conflict_result {
explored_conflict_zones.insert(conflict_zone_id);
possible_cross_conflict = Some(conflict);
}
}
if let Some(conflict) = possible_cross_conflict {
if let Some(v) = vehicles.get_mut(&cell_intention.get_vehicle_id()) {
v.is_conflict_participant = true;
}
conflicts_data.push(conflict);
}
} else {
if verbose.is_at_least(crate::verbose::VerboseLevel::Additional) {
verbose.log_with_fields(
crate::verbose::EVENT_CONFLICTS_COLLECT,
"Inspect multi-vehicle intention",
&[
("cell_intentions_num", &cell_intentions.len()),
("intention_cell", &intention_cell.get_id()),
]
);
}
let conflict = new_conflict_multiple(
intention_cell, conflict_zones, cells_conflicts_zones, cell_intentions, vehicles
)?;
if conflict.conflict_type == ConflictType::SelfTail {
continue;
}
conflicts_data.push(conflict);
}
}
Ok(conflicts_data)
}