solverforge_solver/manager/
builder.rs

1//! Zero-erasure builder for SolverFactory.
2
3use std::marker::PhantomData;
4use std::time::Duration;
5
6use solverforge_config::SolverConfig;
7use solverforge_core::domain::PlanningSolution;
8use solverforge_scoring::ScoreDirector;
9
10use crate::phase::Phase;
11use crate::solver::NoTermination;
12use crate::termination::{OrTermination, StepCountTermination, Termination, TimeTermination};
13
14use super::{PhaseFactory, SolverFactory};
15
16/// Builder for SolverFactory with zero type erasure.
17///
18/// Accumulates configuration and produces a fully typed SolverFactory.
19/// Type bounds are only checked at `build()` time.
20pub struct SolverFactoryBuilder<S, D, C, P, T>
21where
22    S: PlanningSolution,
23{
24    score_calculator: C,
25    phases: P,
26    termination: T,
27    _marker: PhantomData<fn(S, D)>,
28}
29
30impl<S, D, C> SolverFactoryBuilder<S, D, C, (), NoTermination>
31where
32    S: PlanningSolution,
33    C: Fn(&S) -> S::Score + Send + Sync,
34{
35    /// Creates a new builder with a score calculator.
36    pub fn new(score_calculator: C) -> Self {
37        Self {
38            score_calculator,
39            phases: (),
40            termination: NoTermination,
41            _marker: PhantomData,
42        }
43    }
44}
45
46impl<S, D, C, P, T> SolverFactoryBuilder<S, D, C, P, T>
47where
48    S: PlanningSolution,
49{
50    /// Adds a phase, returning a new builder with updated phase tuple.
51    pub fn with_phase<P2>(self, phase: P2) -> SolverFactoryBuilder<S, D, C, (P, P2), T> {
52        SolverFactoryBuilder {
53            score_calculator: self.score_calculator,
54            phases: (self.phases, phase),
55            termination: self.termination,
56            _marker: PhantomData,
57        }
58    }
59
60    /// Adds a phase from a factory, returning a new builder with updated phase tuple.
61    ///
62    /// The factory's `create()` method is called to produce the phase.
63    pub fn with_phase_factory<F>(
64        self,
65        factory: F,
66    ) -> SolverFactoryBuilder<S, D, C, (P, F::Phase), T>
67    where
68        D: ScoreDirector<S>,
69        F: PhaseFactory<S, D>,
70    {
71        let phase = factory.create();
72        SolverFactoryBuilder {
73            score_calculator: self.score_calculator,
74            phases: (self.phases, phase),
75            termination: self.termination,
76            _marker: PhantomData,
77        }
78    }
79
80    /// Applies configuration from a SolverConfig.
81    ///
82    /// Currently applies termination settings from the config.
83    pub fn with_config(
84        self,
85        config: SolverConfig,
86    ) -> SolverFactoryBuilder<S, D, C, P, TimeTermination> {
87        let term = config.termination.unwrap_or_default();
88        let duration = term.time_limit().unwrap_or(Duration::from_secs(30));
89        SolverFactoryBuilder {
90            score_calculator: self.score_calculator,
91            phases: self.phases,
92            termination: TimeTermination::new(duration),
93            _marker: PhantomData,
94        }
95    }
96
97    /// Sets time limit termination.
98    pub fn with_time_limit(
99        self,
100        duration: Duration,
101    ) -> SolverFactoryBuilder<S, D, C, P, TimeTermination> {
102        SolverFactoryBuilder {
103            score_calculator: self.score_calculator,
104            phases: self.phases,
105            termination: TimeTermination::new(duration),
106            _marker: PhantomData,
107        }
108    }
109
110    /// Sets step limit termination.
111    pub fn with_step_limit(
112        self,
113        steps: u64,
114    ) -> SolverFactoryBuilder<S, D, C, P, StepCountTermination> {
115        SolverFactoryBuilder {
116            score_calculator: self.score_calculator,
117            phases: self.phases,
118            termination: StepCountTermination::new(steps),
119            _marker: PhantomData,
120        }
121    }
122
123    /// Combines current termination with time limit.
124    #[allow(clippy::type_complexity)]
125    pub fn with_time_limit_or(
126        self,
127        duration: Duration,
128    ) -> SolverFactoryBuilder<S, D, C, P, OrTermination<(T, TimeTermination), S, D>>
129    where
130        D: ScoreDirector<S>,
131    {
132        SolverFactoryBuilder {
133            score_calculator: self.score_calculator,
134            phases: self.phases,
135            termination: OrTermination::new((self.termination, TimeTermination::new(duration))),
136            _marker: PhantomData,
137        }
138    }
139}
140
141impl<S, D, C, P, T> SolverFactoryBuilder<S, D, C, P, T>
142where
143    S: PlanningSolution,
144    D: ScoreDirector<S>,
145    C: Fn(&S) -> S::Score + Send + Sync,
146    P: Phase<S, D>,
147    T: Termination<S, D>,
148{
149    /// Builds the SolverFactory.
150    ///
151    /// Returns `Ok(SolverFactory)` on success, or `Err` if configuration is invalid.
152    /// Currently always succeeds as validation happens at compile time via type bounds.
153    pub fn build(self) -> Result<SolverFactory<S, D, C, P, T>, SolverBuildError> {
154        Ok(SolverFactory::new(
155            self.score_calculator,
156            self.phases,
157            self.termination,
158        ))
159    }
160}
161
162/// Error type for SolverFactory building.
163#[derive(Debug, Clone)]
164pub enum SolverBuildError {
165    /// Configuration is invalid.
166    InvalidConfig(String),
167}
168
169impl std::fmt::Display for SolverBuildError {
170    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171        match self {
172            SolverBuildError::InvalidConfig(msg) => {
173                write!(f, "Invalid solver configuration: {}", msg)
174            }
175        }
176    }
177}
178
179impl std::error::Error for SolverBuildError {}