Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ use crate::heuristic::selector::list_support::ordered_index;
use crate::heuristic::selector::move_selector::{
CandidateId, CandidateStore, MoveCandidateRef, MoveCursor, MoveStreamContext,
};
use crate::heuristic::selector::nearby_list_support::{
sort_and_limit_nearby_candidates, NearbyCandidate,
};
use crate::heuristic::selector::nearby_list_support::BoundedNearbyCandidates;
use crate::heuristic::selector::precedence_route::PrecedenceRouteGraph;
use crate::list_placement::OwnerRestriction;

Expand All @@ -34,11 +32,10 @@ where
source_idx: usize,
source_pos_offset: usize,
current_source: (usize, usize),
destinations: Vec<(usize, usize)>,
destinations: BoundedNearbyCandidates,
destination_offset: usize,
fixed_to_current_entity: bool,
precedence_route_graph: Option<PrecedenceRouteGraph>,
max_nearby: usize,
descriptor_index: usize,
source_salt: u64,
}
Expand Down Expand Up @@ -73,11 +70,10 @@ where
source_idx: 0,
source_pos_offset: 0,
current_source: (0, 0),
destinations: Vec::new(),
destinations: BoundedNearbyCandidates::new(max_nearby),
destination_offset: 0,
fixed_to_current_entity,
precedence_route_graph: None,
max_nearby,
descriptor_index,
source_salt,
}
Expand All @@ -102,8 +98,6 @@ where
}

fn load_next_source(&mut self, solution: &S) -> bool {
let mut candidates: Vec<NearbyCandidate> = Vec::new();

while self.source_idx < self.entities.len() {
let source_entity = self.entities[self.source_idx];
let source_len = self.route_lens[self.source_idx];
Expand Down Expand Up @@ -131,7 +125,7 @@ where
continue;
}

candidates.clear();
self.destinations.clear();
let source_allowed =
source_restriction.is_none_or(|restriction| restriction.allows(source_entity));
if source_allowed {
Expand Down Expand Up @@ -159,7 +153,11 @@ where
destination_position.min(source_len.saturating_sub(1)),
);
if distance.is_finite() {
candidates.push((source_entity, destination_position, distance));
self.destinations.offer((
source_entity,
destination_position,
distance,
));
}
}
}
Expand Down Expand Up @@ -187,7 +185,7 @@ where
reference_position,
);
if distance.is_finite() {
candidates.push((
self.destinations.offer((
destination_entity,
destination_position,
distance,
Expand All @@ -197,17 +195,10 @@ where
}
}

sort_and_limit_nearby_candidates(&mut candidates, self.max_nearby);
if candidates.is_empty() {
if self.destinations.is_empty() {
continue;
}
self.current_source = (source_entity, source_position);
self.destinations.clear();
self.destinations.extend(
candidates
.iter()
.map(|&(entity, position, _)| (entity, position)),
);
self.destination_offset = 0;
return true;
}
Expand All @@ -224,7 +215,8 @@ where
return None;
}
let (source_entity, source_position) = self.current_source;
let (destination_entity, destination_position) = self.destinations[self.destination_offset];
let (destination_entity, destination_position) =
self.destinations.destination(self.destination_offset);
self.destination_offset += 1;
Some(self.emitter.emit_change(
source_entity,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ use crate::heuristic::selector::list_support::ordered_index;
use crate::heuristic::selector::move_selector::{
CandidateId, CandidateStore, MoveCandidateRef, MoveCursor, MoveStreamContext,
};
use crate::heuristic::selector::nearby_list_support::{
sort_and_limit_nearby_candidates, NearbyCandidate,
};
use crate::heuristic::selector::nearby_list_support::BoundedNearbyCandidates;
use crate::heuristic::selector::precedence_route::PrecedenceRouteGraph;
use crate::list_placement::OwnerRestriction;

Expand All @@ -34,11 +32,10 @@ where
source_idx: usize,
source_pos_offset: usize,
current_source: (usize, usize),
destinations: Vec<(usize, usize)>,
destinations: BoundedNearbyCandidates,
destination_offset: usize,
fixed_to_current_entity: bool,
precedence_route_graph: Option<PrecedenceRouteGraph>,
max_nearby: usize,
descriptor_index: usize,
source_salt: u64,
}
Expand Down Expand Up @@ -73,11 +70,10 @@ where
source_idx: 0,
source_pos_offset: 0,
current_source: (0, 0),
destinations: Vec::new(),
destinations: BoundedNearbyCandidates::new(max_nearby),
destination_offset: 0,
fixed_to_current_entity,
precedence_route_graph: None,
max_nearby,
descriptor_index,
source_salt,
}
Expand All @@ -102,7 +98,6 @@ where
}

fn load_next_source(&mut self, solution: &S) -> bool {
let mut candidates: Vec<NearbyCandidate> = Vec::new();
while self.source_idx < self.entities.len() {
let source_entity = self.entities[self.source_idx];
let source_len = self.route_lens[self.source_idx];
Expand All @@ -129,7 +124,7 @@ where
continue;
}

candidates.clear();
self.destinations.clear();
for destination_position in source_position + 1..source_len {
let destination_restriction = self
.probe
Expand Down Expand Up @@ -163,7 +158,8 @@ where
destination_position,
);
if distance.is_finite() {
candidates.push((source_entity, destination_position, distance));
self.destinations
.offer((source_entity, destination_position, distance));
}
}

Expand Down Expand Up @@ -206,7 +202,7 @@ where
destination_position,
);
if distance.is_finite() {
candidates.push((
self.destinations.offer((
destination_entity,
destination_position,
distance,
Expand All @@ -216,17 +212,10 @@ where
}
}

sort_and_limit_nearby_candidates(&mut candidates, self.max_nearby);
if candidates.is_empty() {
if self.destinations.is_empty() {
continue;
}
self.current_source = (source_entity, source_position);
self.destinations.clear();
self.destinations.extend(
candidates
.iter()
.map(|&(entity, position, _)| (entity, position)),
);
self.destination_offset = 0;
return true;
}
Expand All @@ -243,7 +232,8 @@ where
return None;
}
let (source_entity, source_position) = self.current_source;
let (destination_entity, destination_position) = self.destinations[self.destination_offset];
let (destination_entity, destination_position) =
self.destinations.destination(self.destination_offset);
self.destination_offset += 1;
Some(self.emitter.emit_swap(
source_entity,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,74 +1,78 @@
//! Bounded nearest-destination selection shared by the nearby list cursors.

use std::cmp::Ordering;

/// One nearby destination: `(entity, position, distance)`.
pub(crate) type NearbyCandidate = (usize, usize, f64);

pub(crate) fn sort_and_limit_nearby_candidates(
candidates: &mut Vec<NearbyCandidate>,
max_nearby: usize,
) {
use std::cmp::Ordering;
/// The `limit` nearest destinations offered so far, in ascending distance.
///
/// Destinations at equal distance keep the order they were offered in, so the
/// retained set is exactly what a stable sort by distance followed by a
/// truncation to `limit` would keep. The buffer never grows past `limit` and
/// keeps its capacity across [`Self::clear`], so a cursor reuses one buffer for
/// every source position instead of collecting every reachable destination.
#[derive(Debug)]
pub(crate) struct BoundedNearbyCandidates {
retained: Vec<NearbyCandidate>,
limit: usize,
}

impl BoundedNearbyCandidates {
/// Creates an empty buffer that retains at most `limit` destinations.
///
/// Capacity grows on demand to the number of destinations actually
/// retained, so an unbounded `limit` reserves nothing up front.
pub(crate) fn new(limit: usize) -> Self {
Self {
retained: Vec::new(),
limit,
}
}

if max_nearby == 0 {
candidates.clear();
return;
/// Drops every retained destination while keeping the capacity.
pub(crate) fn clear(&mut self) {
self.retained.clear();
}

let mut retained = 0;
for read in 0..candidates.len() {
let candidate = candidates[read];
let insertion = candidates[..retained].partition_point(|existing| {
/// Offers one destination, keeping it only if it ranks among the nearest
/// `limit` destinations offered since the last [`Self::clear`].
#[inline]
pub(crate) fn offer(&mut self, candidate: NearbyCandidate) {
let insertion = self.retained.partition_point(|existing| {
existing
.2
.partial_cmp(&candidate.2)
.unwrap_or(Ordering::Equal)
!= Ordering::Greater
});
if insertion >= max_nearby {
continue;
if insertion >= self.limit {
return;
}

let next_retained = (retained + 1).min(max_nearby);
candidates.copy_within(insertion..next_retained - 1, insertion + 1);
candidates[insertion] = candidate;
retained = next_retained;
if self.retained.len() == self.limit {
self.retained.pop();
}
self.retained.insert(insertion, candidate);
}
candidates.truncate(retained);
}

#[cfg(test)]
mod tests {
use std::cmp::Ordering;

use super::{sort_and_limit_nearby_candidates, NearbyCandidate};
/// Number of retained destinations.
pub(crate) fn len(&self) -> usize {
self.retained.len()
}

fn stable_sort_and_limit(
mut candidates: Vec<NearbyCandidate>,
max_nearby: usize,
) -> Vec<NearbyCandidate> {
candidates.sort_by(|left, right| left.2.partial_cmp(&right.2).unwrap_or(Ordering::Equal));
candidates.truncate(max_nearby);
candidates
/// Whether no destination is retained.
pub(crate) fn is_empty(&self) -> bool {
self.retained.is_empty()
}

#[test]
fn bounded_order_matches_stable_full_sort() {
for len in 0_usize..96 {
let mut state = 0x9E37_79B9_u32 ^ len as u32;
let candidates = (0..len)
.map(|index| {
state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
let distance = if index % 17 == 0 {
-0.0
} else {
f64::from(state % 11)
};
(index / 7, index, distance)
})
.collect::<Vec<_>>();
for max_nearby in 0..=len + 2 {
let expected = stable_sort_and_limit(candidates.clone(), max_nearby);
let mut actual = candidates.clone();
sort_and_limit_nearby_candidates(&mut actual, max_nearby);
assert_eq!(actual, expected, "len={len}, max_nearby={max_nearby}");
}
}
/// The `(entity, position)` of the retained destination at `index`.
#[inline]
pub(crate) fn destination(&self, index: usize) -> (usize, usize) {
let (entity, position, _) = self.retained[index];
(entity, position)
}
}

#[cfg(test)]
#[path = "nearby_list_support_tests.rs"]
mod tests;
Loading
Loading