1use std::fmt::{self, Debug};
2
3use solverforge_config::{
4 ChangeMoveConfig, ListReverseMoveConfig, LocalSearchConfig, MoveSelectorConfig,
5 NearbyListChangeMoveConfig, NearbyListSwapMoveConfig, VariableTargetConfig, VndConfig,
6};
7use solverforge_core::domain::PlanningSolution;
8use solverforge_core::score::{ParseableScore, Score};
9
10use crate::heuristic::r#move::{EitherMove, ListMoveImpl, Move, MoveArena};
11use crate::heuristic::selector::decorator::VecUnionSelector;
12use crate::heuristic::selector::move_selector::MoveSelector;
13use crate::heuristic::selector::nearby_list_change::CrossEntityDistanceMeter;
14use crate::phase::dynamic_vnd::DynamicVndPhase;
15use crate::phase::localsearch::{
16 AcceptedCountForager, LocalSearchPhase, SimulatedAnnealingAcceptor,
17};
18
19use super::acceptor::{AcceptorBuilder, AnyAcceptor};
20use super::context::ModelContext;
21use super::forager::{AnyForager, ForagerBuilder};
22use super::list_selector::{ListLeafSelector, ListMoveSelectorBuilder};
23use super::standard_selector::{build_standard_move_selector, StandardLeafSelector};
24
25type LeafSelector<S, V, DM, IDM> =
26 VecUnionSelector<S, NeighborhoodMove<S, V>, NeighborhoodLeaf<S, V, DM, IDM>>;
27
28pub enum NeighborhoodMove<S, V> {
29 Scalar(EitherMove<S, usize>),
30 List(ListMoveImpl<S, V>),
31}
32
33impl<S, V> Debug for NeighborhoodMove<S, V>
34where
35 S: PlanningSolution + 'static,
36 V: Clone + PartialEq + Send + Sync + Debug + 'static,
37{
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 Self::Scalar(m) => write!(f, "NeighborhoodMove::Scalar({m:?})"),
41 Self::List(m) => write!(f, "NeighborhoodMove::List({m:?})"),
42 }
43 }
44}
45
46impl<S, V> Move<S> for NeighborhoodMove<S, V>
47where
48 S: PlanningSolution + 'static,
49 V: Clone + PartialEq + Send + Sync + Debug + 'static,
50{
51 fn is_doable<D: solverforge_scoring::Director<S>>(&self, score_director: &D) -> bool {
52 match self {
53 Self::Scalar(m) => m.is_doable(score_director),
54 Self::List(m) => m.is_doable(score_director),
55 }
56 }
57
58 fn do_move<D: solverforge_scoring::Director<S>>(&self, score_director: &mut D) {
59 match self {
60 Self::Scalar(m) => m.do_move(score_director),
61 Self::List(m) => m.do_move(score_director),
62 }
63 }
64
65 fn descriptor_index(&self) -> usize {
66 match self {
67 Self::Scalar(m) => m.descriptor_index(),
68 Self::List(m) => m.descriptor_index(),
69 }
70 }
71
72 fn entity_indices(&self) -> &[usize] {
73 match self {
74 Self::Scalar(m) => m.entity_indices(),
75 Self::List(m) => m.entity_indices(),
76 }
77 }
78
79 fn variable_name(&self) -> &str {
80 match self {
81 Self::Scalar(m) => m.variable_name(),
82 Self::List(m) => m.variable_name(),
83 }
84 }
85}
86
87pub enum NeighborhoodLeaf<S, V, DM, IDM>
88where
89 S: PlanningSolution + 'static,
90 V: Clone + PartialEq + Send + Sync + Debug + 'static,
91 DM: CrossEntityDistanceMeter<S> + Clone,
92 IDM: CrossEntityDistanceMeter<S> + Clone,
93{
94 Scalar(StandardLeafSelector<S>),
95 List(ListLeafSelector<S, V, DM, IDM>),
96}
97
98impl<S, V, DM, IDM> Debug for NeighborhoodLeaf<S, V, DM, IDM>
99where
100 S: PlanningSolution + 'static,
101 V: Clone + PartialEq + Send + Sync + Debug + 'static,
102 DM: CrossEntityDistanceMeter<S> + Clone + Debug,
103 IDM: CrossEntityDistanceMeter<S> + Clone + Debug,
104{
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 match self {
107 Self::Scalar(selector) => write!(f, "NeighborhoodLeaf::Scalar({selector:?})"),
108 Self::List(selector) => write!(f, "NeighborhoodLeaf::List({selector:?})"),
109 }
110 }
111}
112
113impl<S, V, DM, IDM> MoveSelector<S, NeighborhoodMove<S, V>> for NeighborhoodLeaf<S, V, DM, IDM>
114where
115 S: PlanningSolution + 'static,
116 V: Clone + PartialEq + Send + Sync + Debug + 'static,
117 DM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
118 IDM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
119{
120 fn open_cursor<'a, D: solverforge_scoring::Director<S>>(
121 &'a self,
122 score_director: &D,
123 ) -> impl Iterator<Item = NeighborhoodMove<S, V>> + 'a {
124 enum LeafIter<A, B> {
125 Scalar(A),
126 List(B),
127 }
128
129 impl<T, A, B> Iterator for LeafIter<A, B>
130 where
131 A: Iterator<Item = T>,
132 B: Iterator<Item = T>,
133 {
134 type Item = T;
135
136 fn next(&mut self) -> Option<Self::Item> {
137 match self {
138 Self::Scalar(iter) => iter.next(),
139 Self::List(iter) => iter.next(),
140 }
141 }
142 }
143
144 match self {
145 Self::Scalar(selector) => LeafIter::Scalar(
146 selector
147 .open_cursor(score_director)
148 .map(NeighborhoodMove::Scalar),
149 ),
150 Self::List(selector) => LeafIter::List(
151 selector
152 .open_cursor(score_director)
153 .map(NeighborhoodMove::List),
154 ),
155 }
156 }
157
158 fn size<D: solverforge_scoring::Director<S>>(&self, score_director: &D) -> usize {
159 match self {
160 Self::Scalar(selector) => selector.size(score_director),
161 Self::List(selector) => selector.size(score_director),
162 }
163 }
164
165 fn append_moves<D: solverforge_scoring::Director<S>>(
166 &self,
167 score_director: &D,
168 arena: &mut MoveArena<NeighborhoodMove<S, V>>,
169 ) {
170 match self {
171 Self::Scalar(selector) => {
172 arena.extend(
173 selector
174 .open_cursor(score_director)
175 .map(NeighborhoodMove::Scalar),
176 );
177 }
178 Self::List(selector) => {
179 arena.extend(
180 selector
181 .open_cursor(score_director)
182 .map(NeighborhoodMove::List),
183 );
184 }
185 }
186 }
187}
188
189pub enum Neighborhood<S, V, DM, IDM>
190where
191 S: PlanningSolution + 'static,
192 V: Clone + PartialEq + Send + Sync + Debug + 'static,
193 DM: CrossEntityDistanceMeter<S> + Clone,
194 IDM: CrossEntityDistanceMeter<S> + Clone,
195{
196 Flat(LeafSelector<S, V, DM, IDM>),
197 Limited {
198 selector: LeafSelector<S, V, DM, IDM>,
199 selected_count_limit: usize,
200 },
201}
202
203impl<S, V, DM, IDM> Debug for Neighborhood<S, V, DM, IDM>
204where
205 S: PlanningSolution + 'static,
206 V: Clone + PartialEq + Send + Sync + Debug + 'static,
207 DM: CrossEntityDistanceMeter<S> + Clone + Debug,
208 IDM: CrossEntityDistanceMeter<S> + Clone + Debug,
209{
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 match self {
212 Self::Flat(selector) => write!(f, "Neighborhood::Flat({selector:?})"),
213 Self::Limited {
214 selector,
215 selected_count_limit,
216 } => f
217 .debug_struct("Neighborhood::Limited")
218 .field("selector", selector)
219 .field("selected_count_limit", selected_count_limit)
220 .finish(),
221 }
222 }
223}
224
225impl<S, V, DM, IDM> MoveSelector<S, NeighborhoodMove<S, V>> for Neighborhood<S, V, DM, IDM>
226where
227 S: PlanningSolution + 'static,
228 V: Clone + PartialEq + Send + Sync + Debug + 'static,
229 DM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
230 IDM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
231{
232 fn open_cursor<'a, D: solverforge_scoring::Director<S>>(
233 &'a self,
234 score_director: &D,
235 ) -> impl Iterator<Item = NeighborhoodMove<S, V>> + 'a {
236 enum NeighborhoodIter<A, B> {
237 Flat(A),
238 Limited(B),
239 }
240
241 impl<T, A, B> Iterator for NeighborhoodIter<A, B>
242 where
243 A: Iterator<Item = T>,
244 B: Iterator<Item = T>,
245 {
246 type Item = T;
247
248 fn next(&mut self) -> Option<Self::Item> {
249 match self {
250 Self::Flat(iter) => iter.next(),
251 Self::Limited(iter) => iter.next(),
252 }
253 }
254 }
255
256 match self {
257 Self::Flat(selector) => NeighborhoodIter::Flat(selector.open_cursor(score_director)),
258 Self::Limited {
259 selector,
260 selected_count_limit,
261 } => NeighborhoodIter::Limited(
262 selector
263 .open_cursor(score_director)
264 .take(*selected_count_limit),
265 ),
266 }
267 }
268
269 fn size<D: solverforge_scoring::Director<S>>(&self, score_director: &D) -> usize {
270 match self {
271 Self::Flat(selector) => selector.size(score_director),
272 Self::Limited {
273 selector,
274 selected_count_limit,
275 } => selector.size(score_director).min(*selected_count_limit),
276 }
277 }
278}
279
280pub type Selector<S, V, DM, IDM> =
281 VecUnionSelector<S, NeighborhoodMove<S, V>, Neighborhood<S, V, DM, IDM>>;
282
283pub type LocalSearch<S, V, DM, IDM> = LocalSearchPhase<
284 S,
285 NeighborhoodMove<S, V>,
286 Selector<S, V, DM, IDM>,
287 AnyAcceptor<S>,
288 AnyForager<S>,
289>;
290
291pub type Vnd<S, V, DM, IDM> =
292 DynamicVndPhase<S, NeighborhoodMove<S, V>, Neighborhood<S, V, DM, IDM>>;
293
294#[derive(Clone, Copy, Debug, PartialEq, Eq)]
295enum SelectorFamily {
296 Scalar,
297 List,
298 Mixed,
299 Unsupported,
300}
301
302fn selector_family(config: &MoveSelectorConfig) -> SelectorFamily {
303 match config {
304 MoveSelectorConfig::ChangeMoveSelector(_) | MoveSelectorConfig::SwapMoveSelector(_) => {
305 SelectorFamily::Scalar
306 }
307 MoveSelectorConfig::ListChangeMoveSelector(_)
308 | MoveSelectorConfig::NearbyListChangeMoveSelector(_)
309 | MoveSelectorConfig::ListSwapMoveSelector(_)
310 | MoveSelectorConfig::NearbyListSwapMoveSelector(_)
311 | MoveSelectorConfig::SubListChangeMoveSelector(_)
312 | MoveSelectorConfig::SubListSwapMoveSelector(_)
313 | MoveSelectorConfig::ListReverseMoveSelector(_)
314 | MoveSelectorConfig::KOptMoveSelector(_)
315 | MoveSelectorConfig::ListRuinMoveSelector(_) => SelectorFamily::List,
316 MoveSelectorConfig::LimitedNeighborhood(limit) => selector_family(limit.selector.as_ref()),
317 MoveSelectorConfig::UnionMoveSelector(union) => {
318 let mut family = None;
319 for child in &union.selectors {
320 let child_family = selector_family(child);
321 if child_family == SelectorFamily::Unsupported {
322 return SelectorFamily::Unsupported;
323 }
324 family = Some(match family {
325 None => child_family,
326 Some(current) if current == child_family => current,
327 Some(_) => SelectorFamily::Mixed,
328 });
329 if family == Some(SelectorFamily::Mixed) {
330 return SelectorFamily::Mixed;
331 }
332 }
333 family.unwrap_or(SelectorFamily::Mixed)
334 }
335 MoveSelectorConfig::CartesianProductMoveSelector(_) => SelectorFamily::Unsupported,
336 }
337}
338
339fn push_scalar_selector<S, V, DM, IDM>(
340 config: Option<&MoveSelectorConfig>,
341 model: &ModelContext<S, V, DM, IDM>,
342 out: &mut Vec<NeighborhoodLeaf<S, V, DM, IDM>>,
343) where
344 S: PlanningSolution + 'static,
345 V: Clone + PartialEq + Send + Sync + Debug + 'static,
346 DM: CrossEntityDistanceMeter<S> + Clone + 'static,
347 IDM: CrossEntityDistanceMeter<S> + Clone + 'static,
348{
349 let scalar_variables: Vec<_> = model.scalar_variables().copied().collect();
350 if scalar_variables.is_empty() {
351 return;
352 }
353 let selector = build_standard_move_selector(config, &scalar_variables);
354 out.extend(
355 selector
356 .into_selectors()
357 .into_iter()
358 .map(NeighborhoodLeaf::Scalar),
359 );
360}
361
362fn push_list_selector<S, V, DM, IDM>(
363 config: Option<&MoveSelectorConfig>,
364 model: &ModelContext<S, V, DM, IDM>,
365 random_seed: Option<u64>,
366 out: &mut Vec<NeighborhoodLeaf<S, V, DM, IDM>>,
367) where
368 S: PlanningSolution + 'static,
369 V: Clone + PartialEq + Send + Sync + Debug + 'static,
370 DM: CrossEntityDistanceMeter<S> + Clone + 'static,
371 IDM: CrossEntityDistanceMeter<S> + Clone + 'static,
372{
373 for variable in model.list_variables() {
374 let selector = ListMoveSelectorBuilder::build(config, variable, random_seed);
375 out.extend(
376 selector
377 .into_selectors()
378 .into_iter()
379 .map(NeighborhoodLeaf::List),
380 );
381 }
382}
383
384fn build_leaf_selector<S, V, DM, IDM>(
385 config: Option<&MoveSelectorConfig>,
386 model: &ModelContext<S, V, DM, IDM>,
387 random_seed: Option<u64>,
388) -> LeafSelector<S, V, DM, IDM>
389where
390 S: PlanningSolution + 'static,
391 V: Clone + PartialEq + Send + Sync + Debug + 'static,
392 DM: CrossEntityDistanceMeter<S> + Clone + 'static,
393 IDM: CrossEntityDistanceMeter<S> + Clone + 'static,
394{
395 let mut leaves = Vec::new();
396 match config {
397 None => unreachable!("default neighborhoods must be resolved before leaf selection"),
398 Some(MoveSelectorConfig::ChangeMoveSelector(_))
399 | Some(MoveSelectorConfig::SwapMoveSelector(_)) => {
400 push_scalar_selector(config, model, &mut leaves);
401 }
402 Some(MoveSelectorConfig::ListChangeMoveSelector(_))
403 | Some(MoveSelectorConfig::NearbyListChangeMoveSelector(_))
404 | Some(MoveSelectorConfig::ListSwapMoveSelector(_))
405 | Some(MoveSelectorConfig::NearbyListSwapMoveSelector(_))
406 | Some(MoveSelectorConfig::SubListChangeMoveSelector(_))
407 | Some(MoveSelectorConfig::SubListSwapMoveSelector(_))
408 | Some(MoveSelectorConfig::ListReverseMoveSelector(_))
409 | Some(MoveSelectorConfig::KOptMoveSelector(_))
410 | Some(MoveSelectorConfig::ListRuinMoveSelector(_)) => {
411 push_list_selector(config, model, random_seed, &mut leaves);
412 }
413 Some(MoveSelectorConfig::UnionMoveSelector(union)) => {
414 for child in &union.selectors {
415 match selector_family(child) {
416 SelectorFamily::Scalar => {
417 push_scalar_selector(Some(child), model, &mut leaves);
418 }
419 SelectorFamily::List => {
420 push_list_selector(Some(child), model, random_seed, &mut leaves);
421 }
422 SelectorFamily::Mixed => {
423 let nested = build_leaf_selector(Some(child), model, random_seed);
424 leaves.extend(nested.into_selectors());
425 }
426 SelectorFamily::Unsupported => {
427 panic!(
428 "cartesian_product move selectors are not supported in the runtime selector graph"
429 );
430 }
431 }
432 }
433 }
434 Some(MoveSelectorConfig::LimitedNeighborhood(_)) => {
435 panic!("limited_neighborhood must be wrapped at the neighborhood level");
436 }
437 Some(MoveSelectorConfig::CartesianProductMoveSelector(_)) => {
438 panic!(
439 "cartesian_product move selectors are not supported in the runtime selector graph"
440 );
441 }
442 }
443 assert!(
444 !leaves.is_empty(),
445 "move selector configuration produced no neighborhoods",
446 );
447 VecUnionSelector::new(leaves)
448}
449
450fn default_scalar_change_selector() -> MoveSelectorConfig {
451 MoveSelectorConfig::ChangeMoveSelector(ChangeMoveConfig {
452 target: VariableTargetConfig::default(),
453 })
454}
455
456fn default_nearby_list_change_selector() -> MoveSelectorConfig {
457 MoveSelectorConfig::NearbyListChangeMoveSelector(NearbyListChangeMoveConfig {
458 max_nearby: 20,
459 target: VariableTargetConfig::default(),
460 })
461}
462
463fn default_nearby_list_swap_selector() -> MoveSelectorConfig {
464 MoveSelectorConfig::NearbyListSwapMoveSelector(NearbyListSwapMoveConfig {
465 max_nearby: 20,
466 target: VariableTargetConfig::default(),
467 })
468}
469
470fn default_list_reverse_selector() -> MoveSelectorConfig {
471 MoveSelectorConfig::ListReverseMoveSelector(ListReverseMoveConfig {
472 target: VariableTargetConfig::default(),
473 })
474}
475
476fn collect_default_neighborhoods<S, V, DM, IDM>(
477 model: &ModelContext<S, V, DM, IDM>,
478 random_seed: Option<u64>,
479 out: &mut Vec<Neighborhood<S, V, DM, IDM>>,
480) where
481 S: PlanningSolution + 'static,
482 V: Clone + PartialEq + Send + Sync + Debug + 'static,
483 DM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
484 IDM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
485{
486 if model.has_list_variables() {
487 let list_change = default_nearby_list_change_selector();
488 out.push(Neighborhood::Flat(build_leaf_selector(
489 Some(&list_change),
490 model,
491 random_seed,
492 )));
493
494 let list_swap = default_nearby_list_swap_selector();
495 out.push(Neighborhood::Flat(build_leaf_selector(
496 Some(&list_swap),
497 model,
498 random_seed,
499 )));
500
501 let list_reverse = default_list_reverse_selector();
502 out.push(Neighborhood::Flat(build_leaf_selector(
503 Some(&list_reverse),
504 model,
505 random_seed,
506 )));
507 }
508
509 if model.scalar_variables().next().is_some() {
510 let scalar_change = default_scalar_change_selector();
511 out.push(Neighborhood::Flat(build_leaf_selector(
512 Some(&scalar_change),
513 model,
514 random_seed,
515 )));
516 }
517}
518
519fn collect_neighborhoods<S, V, DM, IDM>(
520 config: Option<&MoveSelectorConfig>,
521 model: &ModelContext<S, V, DM, IDM>,
522 random_seed: Option<u64>,
523 out: &mut Vec<Neighborhood<S, V, DM, IDM>>,
524) where
525 S: PlanningSolution + 'static,
526 V: Clone + PartialEq + Send + Sync + Debug + 'static,
527 DM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
528 IDM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
529{
530 match config {
531 None => collect_default_neighborhoods(model, random_seed, out),
532 Some(MoveSelectorConfig::UnionMoveSelector(union)) => {
533 for child in &union.selectors {
534 collect_neighborhoods(Some(child), model, random_seed, out);
535 }
536 }
537 Some(MoveSelectorConfig::LimitedNeighborhood(limit)) => {
538 let selector = build_leaf_selector(Some(limit.selector.as_ref()), model, random_seed);
539 out.push(Neighborhood::Limited {
540 selector,
541 selected_count_limit: limit.selected_count_limit,
542 });
543 }
544 Some(MoveSelectorConfig::CartesianProductMoveSelector(_)) => {
545 panic!(
546 "cartesian_product move selectors are not supported in the runtime selector graph"
547 );
548 }
549 Some(other) => out.push(Neighborhood::Flat(build_leaf_selector(
550 Some(other),
551 model,
552 random_seed,
553 ))),
554 }
555}
556
557pub fn build_move_selector<S, V, DM, IDM>(
558 config: Option<&MoveSelectorConfig>,
559 model: &ModelContext<S, V, DM, IDM>,
560 random_seed: Option<u64>,
561) -> Selector<S, V, DM, IDM>
562where
563 S: PlanningSolution + 'static,
564 V: Clone + PartialEq + Send + Sync + Debug + 'static,
565 DM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
566 IDM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
567{
568 let mut neighborhoods = Vec::new();
569 collect_neighborhoods(config, model, random_seed, &mut neighborhoods);
570 assert!(
571 !neighborhoods.is_empty(),
572 "move selector configuration produced no neighborhoods",
573 );
574 VecUnionSelector::new(neighborhoods)
575}
576
577pub fn build_local_search<S, V, DM, IDM>(
578 config: Option<&LocalSearchConfig>,
579 model: &ModelContext<S, V, DM, IDM>,
580 random_seed: Option<u64>,
581) -> LocalSearch<S, V, DM, IDM>
582where
583 S: PlanningSolution + 'static,
584 S::Score: Score + ParseableScore,
585 V: Clone + PartialEq + Send + Sync + Debug + 'static,
586 DM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
587 IDM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
588{
589 let acceptor = config
590 .and_then(|ls| ls.acceptor.as_ref())
591 .map(|cfg| AcceptorBuilder::build_with_seed::<S>(cfg, random_seed))
592 .unwrap_or_else(|| {
593 if model.has_list_variables() {
594 AnyAcceptor::LateAcceptance(
595 crate::phase::localsearch::LateAcceptanceAcceptor::<S>::new(400),
596 )
597 } else {
598 match random_seed {
599 Some(seed) => AnyAcceptor::SimulatedAnnealing(
600 SimulatedAnnealingAcceptor::auto_calibrate_with_seed(0.999985, seed),
601 ),
602 None => AnyAcceptor::SimulatedAnnealing(SimulatedAnnealingAcceptor::default()),
603 }
604 }
605 });
606 let forager = config
607 .and_then(|ls| ls.forager.as_ref())
608 .map(|cfg| ForagerBuilder::build::<S>(Some(cfg)))
609 .unwrap_or_else(|| {
610 let accepted = if model.has_list_variables() { 4 } else { 1 };
611 AnyForager::AcceptedCount(AcceptedCountForager::new(accepted))
612 });
613 let move_selector = build_move_selector(
614 config.and_then(|ls| ls.move_selector.as_ref()),
615 model,
616 random_seed,
617 );
618
619 LocalSearchPhase::new(move_selector, acceptor, forager, None)
620}
621
622pub fn build_vnd<S, V, DM, IDM>(
623 config: &VndConfig,
624 model: &ModelContext<S, V, DM, IDM>,
625 random_seed: Option<u64>,
626) -> Vnd<S, V, DM, IDM>
627where
628 S: PlanningSolution + 'static,
629 S::Score: Score + ParseableScore,
630 V: Clone + PartialEq + Send + Sync + Debug + 'static,
631 DM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
632 IDM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
633{
634 let neighborhoods = if config.neighborhoods.is_empty() {
635 let mut neighborhoods = Vec::new();
636 collect_neighborhoods(None, model, random_seed, &mut neighborhoods);
637 neighborhoods
638 } else {
639 config
640 .neighborhoods
641 .iter()
642 .flat_map(|selector| {
643 let mut neighborhoods = Vec::new();
644 collect_neighborhoods(Some(selector), model, random_seed, &mut neighborhoods);
645 neighborhoods
646 })
647 .collect()
648 };
649
650 DynamicVndPhase::new(neighborhoods)
651}
652
653#[cfg(test)]
654#[path = "selector_tests.rs"]
655mod tests;