Skip to main content

solverforge_solver/builder/
selector.rs

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