Skip to main content

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::Director;
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    pub fn new(score_calculator: C) -> Self {
36        Self {
37            score_calculator,
38            phases: (),
39            termination: NoTermination,
40            _marker: PhantomData,
41        }
42    }
43}
44
45impl<S, D, C, P, T> SolverFactoryBuilder<S, D, C, P, T>
46where
47    S: PlanningSolution,
48{
49    pub fn with_phase<P2>(self, phase: P2) -> SolverFactoryBuilder<S, D, C, (P, P2), T> {
50        SolverFactoryBuilder {
51            score_calculator: self.score_calculator,
52            phases: (self.phases, phase),
53            termination: self.termination,
54            _marker: PhantomData,
55        }
56    }
57
58    /// Adds a phase from a factory, returning a new builder with updated phase tuple.
59    ///
60    /// The factory's `create()` method is called to produce the phase.
61    pub fn with_phase_factory<F>(
62        self,
63        factory: F,
64    ) -> SolverFactoryBuilder<S, D, C, (P, F::Phase), T>
65    where
66        D: Director<S>,
67        F: PhaseFactory<S, D>,
68    {
69        let phase = factory.create();
70        SolverFactoryBuilder {
71            score_calculator: self.score_calculator,
72            phases: (self.phases, phase),
73            termination: self.termination,
74            _marker: PhantomData,
75        }
76    }
77
78    pub fn with_config(
79        self,
80        config: SolverConfig,
81    ) -> SolverFactoryBuilder<S, D, C, P, TimeTermination> {
82        let term = config.termination.unwrap_or_default();
83        let duration = term.time_limit().unwrap_or(Duration::from_secs(30));
84        SolverFactoryBuilder {
85            score_calculator: self.score_calculator,
86            phases: self.phases,
87            termination: TimeTermination::new(duration),
88            _marker: PhantomData,
89        }
90    }
91
92    pub fn with_time_limit(
93        self,
94        duration: Duration,
95    ) -> SolverFactoryBuilder<S, D, C, P, TimeTermination> {
96        SolverFactoryBuilder {
97            score_calculator: self.score_calculator,
98            phases: self.phases,
99            termination: TimeTermination::new(duration),
100            _marker: PhantomData,
101        }
102    }
103
104    pub fn with_step_limit(
105        self,
106        steps: u64,
107    ) -> SolverFactoryBuilder<S, D, C, P, StepCountTermination> {
108        SolverFactoryBuilder {
109            score_calculator: self.score_calculator,
110            phases: self.phases,
111            termination: StepCountTermination::new(steps),
112            _marker: PhantomData,
113        }
114    }
115
116    // Combines current termination with time limit.
117    #[allow(clippy::type_complexity)]
118    pub fn with_time_limit_or(
119        self,
120        duration: Duration,
121    ) -> SolverFactoryBuilder<S, D, C, P, OrTermination<(T, TimeTermination), S, D>>
122    where
123        D: Director<S>,
124    {
125        SolverFactoryBuilder {
126            score_calculator: self.score_calculator,
127            phases: self.phases,
128            termination: OrTermination::new((self.termination, TimeTermination::new(duration))),
129            _marker: PhantomData,
130        }
131    }
132}
133
134impl<S, D, C, P, T> SolverFactoryBuilder<S, D, C, P, T>
135where
136    S: PlanningSolution,
137    D: Director<S>,
138    C: Fn(&S) -> S::Score + Send + Sync,
139    P: Phase<S, D>,
140    T: Termination<S, D>,
141{
142    /// Builds the SolverFactory.
143    ///
144    /// Returns `Ok(SolverFactory)` on success, or `Err` if configuration is invalid.
145    /// Currently always succeeds as validation happens at compile time via type bounds.
146    pub fn build(self) -> Result<SolverFactory<S, D, C, P, T>, SolverBuildError> {
147        Ok(SolverFactory::new(
148            self.score_calculator,
149            self.phases,
150            self.termination,
151        ))
152    }
153}
154
155// Error type for SolverFactory building.
156#[derive(Debug, Clone)]
157pub enum SolverBuildError {
158    // Configuration is invalid.
159    InvalidConfig(String),
160}
161
162impl std::fmt::Display for SolverBuildError {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        match self {
165            SolverBuildError::InvalidConfig(msg) => {
166                write!(f, "Invalid solver configuration: {}", msg)
167            }
168        }
169    }
170}
171
172impl std::error::Error for SolverBuildError {}