solverforge_core/solver/
factory.rs

1use crate::bridge::LanguageBridge;
2use crate::constraints::ConstraintSet;
3use crate::domain::DomainModel;
4use crate::error::{SolverForgeError, SolverForgeResult};
5use crate::solver::{
6    HttpSolverService, SolveHandle, SolveRequest, SolveResponse, SolveStatus, SolverConfig,
7    SolverService,
8};
9use crate::ObjectHandle;
10use std::marker::PhantomData;
11use std::sync::Arc;
12
13pub struct SolverFactory<B: LanguageBridge> {
14    config: SolverConfig,
15    service: Arc<dyn SolverService>,
16    domain_model: DomainModel,
17    constraints: ConstraintSet,
18    wasm_module: String,
19    _bridge: PhantomData<B>,
20}
21
22impl<B: LanguageBridge> SolverFactory<B> {
23    pub fn create(
24        config: SolverConfig,
25        service_url: impl Into<String>,
26        domain_model: DomainModel,
27        constraints: ConstraintSet,
28        wasm_module: String,
29    ) -> Self {
30        let service = Arc::new(HttpSolverService::new(service_url));
31        Self {
32            config,
33            service,
34            domain_model,
35            constraints,
36            wasm_module,
37            _bridge: PhantomData,
38        }
39    }
40
41    pub fn with_service(
42        config: SolverConfig,
43        service: Arc<dyn SolverService>,
44        domain_model: DomainModel,
45        constraints: ConstraintSet,
46        wasm_module: String,
47    ) -> Self {
48        Self {
49            config,
50            service,
51            domain_model,
52            constraints,
53            wasm_module,
54            _bridge: PhantomData,
55        }
56    }
57
58    pub fn build_solver(&self, bridge: Arc<B>) -> Solver<B> {
59        Solver {
60            config: self.config.clone(),
61            service: self.service.clone(),
62            domain_model: self.domain_model.clone(),
63            constraints: self.constraints.clone(),
64            wasm_module: self.wasm_module.clone(),
65            bridge,
66        }
67    }
68
69    pub fn config(&self) -> &SolverConfig {
70        &self.config
71    }
72
73    pub fn domain_model(&self) -> &DomainModel {
74        &self.domain_model
75    }
76
77    pub fn constraints(&self) -> &ConstraintSet {
78        &self.constraints
79    }
80
81    pub fn is_service_available(&self) -> bool {
82        self.service.is_available()
83    }
84}
85
86pub struct Solver<B: LanguageBridge> {
87    config: SolverConfig,
88    service: Arc<dyn SolverService>,
89    domain_model: DomainModel,
90    constraints: ConstraintSet,
91    wasm_module: String,
92    bridge: Arc<B>,
93}
94
95impl<B: LanguageBridge> Solver<B> {
96    pub fn solve(&self, problem: ObjectHandle) -> SolverForgeResult<SolveResponse> {
97        let request = self.build_request(problem)?;
98        self.service.solve(&request)
99    }
100
101    pub fn solve_async(&self, problem: ObjectHandle) -> SolverForgeResult<SolveHandle> {
102        let request = self.build_request(problem)?;
103        self.service.solve_async(&request)
104    }
105
106    pub fn get_status(&self, handle: &SolveHandle) -> SolverForgeResult<SolveStatus> {
107        self.service.get_status(handle)
108    }
109
110    pub fn get_best_solution(
111        &self,
112        handle: &SolveHandle,
113    ) -> SolverForgeResult<Option<SolveResponse>> {
114        self.service.get_best_solution(handle)
115    }
116
117    pub fn stop(&self, handle: &SolveHandle) -> SolverForgeResult<()> {
118        self.service.stop(handle)
119    }
120
121    pub fn config(&self) -> &SolverConfig {
122        &self.config
123    }
124
125    pub fn bridge(&self) -> &Arc<B> {
126        &self.bridge
127    }
128
129    pub fn service(&self) -> &Arc<dyn SolverService> {
130        &self.service
131    }
132
133    pub fn domain_model(&self) -> &DomainModel {
134        &self.domain_model
135    }
136
137    pub fn constraints(&self) -> &ConstraintSet {
138        &self.constraints
139    }
140
141    pub fn wasm_module(&self) -> &str {
142        &self.wasm_module
143    }
144
145    fn build_request(&self, problem: ObjectHandle) -> SolverForgeResult<SolveRequest> {
146        let problem_json = self
147            .bridge
148            .serialize_object(problem)
149            .map_err(|e| SolverForgeError::Bridge(format!("Failed to serialize problem: {}", e)))?;
150
151        let domain_dto = self.domain_model.to_dto();
152        let constraints_dto = self.constraints.to_dto();
153
154        let list_accessor = crate::solver::ListAccessorDto::new(
155            "create_list",
156            "get_item",
157            "set_item",
158            "get_size",
159            "append",
160            "insert",
161            "remove",
162            "deallocate_list",
163        );
164
165        let mut request = SolveRequest::new(
166            domain_dto,
167            constraints_dto,
168            self.wasm_module.clone(),
169            "allocate".to_string(),
170            "deallocate".to_string(),
171            list_accessor,
172            problem_json,
173        );
174
175        if let Some(mode) = &self.config.environment_mode {
176            request = request.with_environment_mode(format!("{:?}", mode).to_uppercase());
177        }
178
179        if let Some(termination) = &self.config.termination {
180            request = request.with_termination(termination.clone());
181        }
182
183        Ok(request)
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use crate::bridge::tests::MockBridge;
191    use crate::constraints::Constraint;
192    use crate::domain::DomainModelBuilder;
193
194    fn create_test_domain() -> DomainModel {
195        DomainModelBuilder::new()
196            .solution_class("Timetable")
197            .entity_class("Lesson")
198            .build()
199    }
200
201    fn create_test_constraints() -> ConstraintSet {
202        ConstraintSet::new().with_constraint(Constraint::new("testConstraint"))
203    }
204
205    #[test]
206    fn test_solver_factory_create() {
207        let factory = SolverFactory::<MockBridge>::create(
208            SolverConfig::new(),
209            "http://localhost:8080",
210            create_test_domain(),
211            create_test_constraints(),
212            "AGFzbQ==".to_string(),
213        );
214
215        assert!(factory.config().solution_class.is_none());
216        assert_eq!(factory.domain_model().solution_class(), Some("Timetable"));
217        assert_eq!(factory.constraints().len(), 1);
218    }
219
220    #[test]
221    fn test_solver_factory_build_solver() {
222        let factory = SolverFactory::create(
223            SolverConfig::new().with_solution_class("Timetable"),
224            "http://localhost:8080",
225            create_test_domain(),
226            create_test_constraints(),
227            "AGFzbQ==".to_string(),
228        );
229
230        let bridge = Arc::new(MockBridge::new());
231        let solver = factory.build_solver(bridge);
232
233        assert_eq!(
234            solver.config().solution_class,
235            Some("Timetable".to_string())
236        );
237    }
238
239    #[test]
240    fn test_solver_factory_is_service_available_offline() {
241        let factory = SolverFactory::<MockBridge>::create(
242            SolverConfig::new(),
243            "http://localhost:19999",
244            create_test_domain(),
245            create_test_constraints(),
246            "AGFzbQ==".to_string(),
247        );
248
249        assert!(!factory.is_service_available());
250    }
251
252    #[test]
253    fn test_solver_config_access() {
254        let config = SolverConfig::new()
255            .with_solution_class("Timetable")
256            .with_random_seed(42);
257
258        let factory = SolverFactory::<MockBridge>::create(
259            config,
260            "http://localhost:8080",
261            create_test_domain(),
262            create_test_constraints(),
263            "AGFzbQ==".to_string(),
264        );
265
266        assert_eq!(factory.config().random_seed, Some(42));
267    }
268
269    #[test]
270    fn test_solver_bridge_access() {
271        let factory = SolverFactory::create(
272            SolverConfig::new(),
273            "http://localhost:8080",
274            create_test_domain(),
275            create_test_constraints(),
276            "AGFzbQ==".to_string(),
277        );
278
279        let bridge = Arc::new(MockBridge::new());
280        let solver = factory.build_solver(bridge.clone());
281
282        assert!(Arc::ptr_eq(solver.bridge(), &bridge));
283    }
284}