Skip to main content

solverforge_solver/builder/search/
custom.rs

1use std::fmt::{self, Debug};
2use std::marker::PhantomData;
3
4use solverforge_config::PartitionedSearchConfig;
5use solverforge_core::domain::PlanningSolution;
6use solverforge_scoring::Director;
7
8use crate::heuristic::r#move::Move;
9use crate::phase::localsearch::MoveCursorSource;
10use crate::phase::localsearch::{Acceptor, LocalSearchForager, LocalSearchPhase};
11use crate::phase::partitioned::{ChildPhases, PartitionedSearchPhase, SolutionPartitioner};
12use crate::phase::Phase;
13use crate::scope::{ProgressCallback, SolverScope};
14
15use super::SearchContext;
16
17pub trait CustomSearchPhase<S>: Debug + Send
18where
19    S: PlanningSolution,
20{
21    fn solve<D, ProgressCb>(&mut self, solver_scope: &mut SolverScope<'_, S, D, ProgressCb>)
22    where
23        D: Director<S>,
24        ProgressCb: ProgressCallback<S>;
25
26    /// Runs once at the enclosing solver's terminal boundary.
27    ///
28    /// This mirrors [`Phase::on_solver_terminal`] for custom phases. The
29    /// configured runtime dispatches it after instantiating the registered
30    /// extension; implementations remain `CustomSearchPhase` rather than
31    /// directly implementing `Phase`.
32    fn on_solver_terminal<D, ProgressCb>(
33        &mut self,
34        _solver_scope: &mut SolverScope<'_, S, D, ProgressCb>,
35    ) where
36        D: Director<S>,
37        ProgressCb: ProgressCallback<S>,
38    {
39    }
40
41    fn phase_type_name(&self) -> &'static str {
42        "CustomSearchPhase"
43    }
44}
45
46impl<S, M, Source, A, Fo> CustomSearchPhase<S> for LocalSearchPhase<S, M, Source, A, Fo>
47where
48    S: PlanningSolution,
49    M: Move<S>,
50    Source: MoveCursorSource<S, M> + Debug + Send,
51    A: Acceptor<S> + Debug,
52    Fo: LocalSearchForager<S, M> + Debug,
53{
54    fn solve<D, ProgressCb>(&mut self, solver_scope: &mut SolverScope<'_, S, D, ProgressCb>)
55    where
56        D: Director<S>,
57        ProgressCb: ProgressCallback<S>,
58    {
59        Phase::solve(self, solver_scope);
60    }
61
62    fn phase_type_name(&self) -> &'static str {
63        "LocalSearchPhase"
64    }
65
66    fn on_solver_terminal<D, ProgressCb>(
67        &mut self,
68        solver_scope: &mut SolverScope<'_, S, D, ProgressCb>,
69    ) where
70        D: Director<S>,
71        ProgressCb: ProgressCallback<S>,
72    {
73        Phase::on_solver_terminal(self, solver_scope);
74    }
75}
76
77impl<S, PD, Part, SDF, PF, CP> CustomSearchPhase<S>
78    for PartitionedSearchPhase<S, PD, Part, SDF, PF, CP>
79where
80    S: PlanningSolution + 'static,
81    PD: Director<S> + 'static,
82    Part: SolutionPartitioner<S>,
83    SDF: Fn(S) -> PD + Send + Sync,
84    PF: Fn() -> CP + Send + Sync,
85    CP: ChildPhases<S, PD> + Send,
86{
87    fn solve<D, ProgressCb>(&mut self, solver_scope: &mut SolverScope<'_, S, D, ProgressCb>)
88    where
89        D: Director<S>,
90        ProgressCb: ProgressCallback<S>,
91    {
92        Phase::solve(self, solver_scope);
93    }
94
95    fn phase_type_name(&self) -> &'static str {
96        "PartitionedSearch"
97    }
98
99    fn on_solver_terminal<D, ProgressCb>(
100        &mut self,
101        solver_scope: &mut SolverScope<'_, S, D, ProgressCb>,
102    ) where
103        D: Director<S>,
104        ProgressCb: ProgressCallback<S>,
105    {
106        Phase::on_solver_terminal(self, solver_scope);
107    }
108}
109
110/// Policy carried by one concrete extension registry.
111///
112/// Typed models may register monomorphized custom or partitioned extensions.
113/// Dynamic models carry a distinct empty registry so compilation can reject
114/// those declarations without asking a host binding to emulate them.
115#[derive(Clone, Copy, Debug, PartialEq, Eq)]
116pub enum RuntimeExtensionPolicy {
117    Typed,
118    Dynamic,
119}
120
121/// Recursive, concrete extension registry owned by a compiled runtime graph.
122///
123/// Compilation consults only the registration predicates. Instantiation is
124/// deferred to the graph executor, which creates fresh concrete phases from
125/// its frozen [`SearchContext`] once for each solve.
126pub trait RuntimeExtensionRegistry<S, V, DM, IDM>
127where
128    S: PlanningSolution,
129{
130    type Phase: CustomSearchPhase<S>;
131
132    fn policy(&self) -> RuntimeExtensionPolicy;
133
134    fn contains_custom(&self, name: &str) -> bool;
135
136    fn instantiate_custom(
137        &self,
138        name: &str,
139        context: &SearchContext<S, V, DM, IDM>,
140    ) -> Option<Self::Phase>;
141
142    fn contains_partitioned(&self, name: &str) -> bool;
143
144    fn instantiate_partitioned(
145        &self,
146        name: &str,
147        config: &PartitionedSearchConfig,
148        context: &SearchContext<S, V, DM, IDM>,
149    ) -> Option<Self::Phase>;
150}
151
152pub struct NoTypedExtensions;
153
154impl<S, V, DM, IDM> RuntimeExtensionRegistry<S, V, DM, IDM> for NoTypedExtensions
155where
156    S: PlanningSolution,
157{
158    type Phase = NoRuntimeExtensionPhase;
159
160    fn policy(&self) -> RuntimeExtensionPolicy {
161        RuntimeExtensionPolicy::Typed
162    }
163
164    fn contains_custom(&self, _name: &str) -> bool {
165        false
166    }
167
168    fn instantiate_custom(
169        &self,
170        _name: &str,
171        _context: &SearchContext<S, V, DM, IDM>,
172    ) -> Option<Self::Phase> {
173        None
174    }
175
176    fn contains_partitioned(&self, _name: &str) -> bool {
177        false
178    }
179
180    fn instantiate_partitioned(
181        &self,
182        _name: &str,
183        _config: &PartitionedSearchConfig,
184        _context: &SearchContext<S, V, DM, IDM>,
185    ) -> Option<Self::Phase> {
186        None
187    }
188}
189
190/// Empty registry used by host-language dynamic models.
191///
192/// Its distinct policy makes unsupported custom and partitioned declarations
193/// a compile-time graph error; no extension builder exists or is invoked.
194pub struct NoDynamicExtensions;
195
196impl<S, V, DM, IDM> RuntimeExtensionRegistry<S, V, DM, IDM> for NoDynamicExtensions
197where
198    S: PlanningSolution,
199{
200    type Phase = NoRuntimeExtensionPhase;
201
202    fn policy(&self) -> RuntimeExtensionPolicy {
203        RuntimeExtensionPolicy::Dynamic
204    }
205
206    fn contains_custom(&self, _name: &str) -> bool {
207        false
208    }
209
210    fn instantiate_custom(
211        &self,
212        _name: &str,
213        _context: &SearchContext<S, V, DM, IDM>,
214    ) -> Option<Self::Phase> {
215        None
216    }
217
218    fn contains_partitioned(&self, _name: &str) -> bool {
219        false
220    }
221
222    fn instantiate_partitioned(
223        &self,
224        _name: &str,
225        _config: &PartitionedSearchConfig,
226        _context: &SearchContext<S, V, DM, IDM>,
227    ) -> Option<Self::Phase> {
228        None
229    }
230}
231
232#[derive(Clone, Copy)]
233pub enum NoRuntimeExtensionPhase {}
234
235impl Debug for NoRuntimeExtensionPhase {
236    fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
237        match *self {}
238    }
239}
240
241impl<S> CustomSearchPhase<S> for NoRuntimeExtensionPhase
242where
243    S: PlanningSolution,
244{
245    fn solve<D, ProgressCb>(&mut self, _solver_scope: &mut SolverScope<'_, S, D, ProgressCb>)
246    where
247        D: Director<S>,
248        ProgressCb: ProgressCallback<S>,
249    {
250        match *self {}
251    }
252
253    fn phase_type_name(&self) -> &'static str {
254        match *self {}
255    }
256}
257
258impl<S, D, ProgressCb> Phase<S, D, ProgressCb> for NoRuntimeExtensionPhase
259where
260    S: PlanningSolution,
261    D: Director<S>,
262    ProgressCb: ProgressCallback<S>,
263{
264    fn solve(&mut self, _solver_scope: &mut SolverScope<'_, S, D, ProgressCb>) {
265        match *self {}
266    }
267
268    fn phase_type_name(&self) -> &'static str {
269        match *self {}
270    }
271}
272
273pub struct CustomPhaseNode<Previous, Builder, Phase> {
274    previous: Previous,
275    name: &'static str,
276    builder: Builder,
277    _marker: PhantomData<fn() -> Phase>,
278}
279
280pub struct PartitionedPhaseNode<Previous, Builder, Phase> {
281    previous: Previous,
282    name: &'static str,
283    builder: Builder,
284    _marker: PhantomData<fn() -> Phase>,
285}
286
287impl<Previous, Builder, Phase> PartitionedPhaseNode<Previous, Builder, Phase> {
288    pub fn new(previous: Previous, name: &'static str, builder: Builder) -> Self {
289        Self {
290            previous,
291            name,
292            builder,
293            _marker: PhantomData,
294        }
295    }
296}
297
298impl<Previous, Builder, Phase> CustomPhaseNode<Previous, Builder, Phase> {
299    pub fn new(previous: Previous, name: &'static str, builder: Builder) -> Self {
300        Self {
301            previous,
302            name,
303            builder,
304            _marker: PhantomData,
305        }
306    }
307}
308
309pub enum CustomPhaseUnion<PreviousPhase, CurrentPhase> {
310    Previous(PreviousPhase),
311    Current(CurrentPhase),
312}
313
314impl<PreviousPhase: Debug, CurrentPhase: Debug> Debug
315    for CustomPhaseUnion<PreviousPhase, CurrentPhase>
316{
317    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318        match self {
319            Self::Previous(phase) => f.debug_tuple("CustomPhase::Previous").field(phase).finish(),
320            Self::Current(phase) => f.debug_tuple("CustomPhase::Current").field(phase).finish(),
321        }
322    }
323}
324
325impl<S, PreviousPhase, CurrentPhase> CustomSearchPhase<S>
326    for CustomPhaseUnion<PreviousPhase, CurrentPhase>
327where
328    S: PlanningSolution,
329    PreviousPhase: CustomSearchPhase<S>,
330    CurrentPhase: CustomSearchPhase<S>,
331{
332    fn solve<D, ProgressCb>(&mut self, solver_scope: &mut SolverScope<'_, S, D, ProgressCb>)
333    where
334        D: Director<S>,
335        ProgressCb: ProgressCallback<S>,
336    {
337        match self {
338            Self::Previous(phase) => phase.solve(solver_scope),
339            Self::Current(phase) => phase.solve(solver_scope),
340        }
341    }
342
343    fn phase_type_name(&self) -> &'static str {
344        "CustomPhase"
345    }
346
347    fn on_solver_terminal<D, ProgressCb>(
348        &mut self,
349        solver_scope: &mut SolverScope<'_, S, D, ProgressCb>,
350    ) where
351        D: Director<S>,
352        ProgressCb: ProgressCallback<S>,
353    {
354        match self {
355            Self::Previous(phase) => phase.on_solver_terminal(solver_scope),
356            Self::Current(phase) => phase.on_solver_terminal(solver_scope),
357        }
358    }
359}
360
361impl<S, D, ProgressCb, PreviousPhase, CurrentPhase> Phase<S, D, ProgressCb>
362    for CustomPhaseUnion<PreviousPhase, CurrentPhase>
363where
364    S: PlanningSolution,
365    D: Director<S>,
366    ProgressCb: ProgressCallback<S>,
367    PreviousPhase: CustomSearchPhase<S>,
368    CurrentPhase: CustomSearchPhase<S>,
369{
370    fn solve(&mut self, solver_scope: &mut SolverScope<'_, S, D, ProgressCb>) {
371        CustomSearchPhase::solve(self, solver_scope);
372    }
373
374    fn phase_type_name(&self) -> &'static str {
375        CustomSearchPhase::<S>::phase_type_name(self)
376    }
377
378    fn on_solver_terminal(&mut self, solver_scope: &mut SolverScope<'_, S, D, ProgressCb>) {
379        CustomSearchPhase::on_solver_terminal(self, solver_scope);
380    }
381}
382
383impl<S, V, DM, IDM, Previous, Builder, CurrentPhase> RuntimeExtensionRegistry<S, V, DM, IDM>
384    for CustomPhaseNode<Previous, Builder, CurrentPhase>
385where
386    S: PlanningSolution,
387    Previous: RuntimeExtensionRegistry<S, V, DM, IDM>,
388    Builder: Fn(&SearchContext<S, V, DM, IDM>) -> CurrentPhase,
389    CurrentPhase: CustomSearchPhase<S>,
390{
391    type Phase = CustomPhaseUnion<Previous::Phase, CurrentPhase>;
392
393    fn policy(&self) -> RuntimeExtensionPolicy {
394        self.previous.policy()
395    }
396
397    fn contains_custom(&self, name: &str) -> bool {
398        self.name == name || self.previous.contains_custom(name)
399    }
400
401    fn instantiate_custom(
402        &self,
403        name: &str,
404        context: &SearchContext<S, V, DM, IDM>,
405    ) -> Option<Self::Phase> {
406        if self.name == name {
407            return Some(CustomPhaseUnion::Current((self.builder)(context)));
408        }
409        self.previous
410            .instantiate_custom(name, context)
411            .map(CustomPhaseUnion::Previous)
412    }
413
414    fn contains_partitioned(&self, name: &str) -> bool {
415        self.previous.contains_partitioned(name)
416    }
417
418    fn instantiate_partitioned(
419        &self,
420        name: &str,
421        config: &PartitionedSearchConfig,
422        context: &SearchContext<S, V, DM, IDM>,
423    ) -> Option<Self::Phase> {
424        self.previous
425            .instantiate_partitioned(name, config, context)
426            .map(CustomPhaseUnion::Previous)
427    }
428}
429
430impl<S, V, DM, IDM, Previous, Builder, CurrentPhase> RuntimeExtensionRegistry<S, V, DM, IDM>
431    for PartitionedPhaseNode<Previous, Builder, CurrentPhase>
432where
433    S: PlanningSolution,
434    Previous: RuntimeExtensionRegistry<S, V, DM, IDM>,
435    Builder: Fn(&SearchContext<S, V, DM, IDM>, &PartitionedSearchConfig) -> CurrentPhase,
436    CurrentPhase: CustomSearchPhase<S>,
437{
438    type Phase = CustomPhaseUnion<Previous::Phase, CurrentPhase>;
439
440    fn policy(&self) -> RuntimeExtensionPolicy {
441        self.previous.policy()
442    }
443
444    fn contains_custom(&self, name: &str) -> bool {
445        self.previous.contains_custom(name)
446    }
447
448    fn instantiate_custom(
449        &self,
450        name: &str,
451        context: &SearchContext<S, V, DM, IDM>,
452    ) -> Option<Self::Phase> {
453        self.previous
454            .instantiate_custom(name, context)
455            .map(CustomPhaseUnion::Previous)
456    }
457
458    fn contains_partitioned(&self, name: &str) -> bool {
459        self.name == name || self.previous.contains_partitioned(name)
460    }
461
462    fn instantiate_partitioned(
463        &self,
464        name: &str,
465        config: &PartitionedSearchConfig,
466        context: &SearchContext<S, V, DM, IDM>,
467    ) -> Option<Self::Phase> {
468        if self.name == name {
469            return Some(CustomPhaseUnion::Current((self.builder)(context, config)));
470        }
471        self.previous
472            .instantiate_partitioned(name, config, context)
473            .map(CustomPhaseUnion::Previous)
474    }
475}