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            "newList", "getItem", "setItem", "size", "append", "insert", "remove", "dealloc",
156        );
157
158        let mut request = SolveRequest::new(
159            domain_dto,
160            constraints_dto,
161            self.wasm_module.clone(),
162            "alloc".to_string(),
163            "dealloc".to_string(),
164            list_accessor,
165            problem_json,
166        );
167
168        if let Some(mode) = &self.config.environment_mode {
169            request = request.with_environment_mode(format!("{:?}", mode).to_uppercase());
170        }
171
172        if let Some(termination) = &self.config.termination {
173            request = request.with_termination(termination.clone());
174        }
175
176        Ok(request)
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183    use crate::bridge::tests::MockBridge;
184    use crate::constraints::Constraint;
185    use crate::domain::DomainModelBuilder;
186
187    fn create_test_domain() -> DomainModel {
188        DomainModelBuilder::new()
189            .solution_class("Timetable")
190            .entity_class("Lesson")
191            .build()
192    }
193
194    fn create_test_constraints() -> ConstraintSet {
195        ConstraintSet::new().with_constraint(Constraint::new("testConstraint"))
196    }
197
198    #[test]
199    fn test_solver_factory_create() {
200        let factory = SolverFactory::<MockBridge>::create(
201            SolverConfig::new(),
202            "http://localhost:8080",
203            create_test_domain(),
204            create_test_constraints(),
205            "AGFzbQ==".to_string(),
206        );
207
208        assert!(factory.config().solution_class.is_none());
209        assert_eq!(factory.domain_model().solution_class(), Some("Timetable"));
210        assert_eq!(factory.constraints().len(), 1);
211    }
212
213    #[test]
214    fn test_solver_factory_build_solver() {
215        let factory = SolverFactory::create(
216            SolverConfig::new().with_solution_class("Timetable"),
217            "http://localhost:8080",
218            create_test_domain(),
219            create_test_constraints(),
220            "AGFzbQ==".to_string(),
221        );
222
223        let bridge = Arc::new(MockBridge::new());
224        let solver = factory.build_solver(bridge);
225
226        assert_eq!(
227            solver.config().solution_class,
228            Some("Timetable".to_string())
229        );
230    }
231
232    #[test]
233    fn test_solver_factory_is_service_available_offline() {
234        let factory = SolverFactory::<MockBridge>::create(
235            SolverConfig::new(),
236            "http://localhost:19999",
237            create_test_domain(),
238            create_test_constraints(),
239            "AGFzbQ==".to_string(),
240        );
241
242        assert!(!factory.is_service_available());
243    }
244
245    #[test]
246    fn test_solver_config_access() {
247        let config = SolverConfig::new()
248            .with_solution_class("Timetable")
249            .with_random_seed(42);
250
251        let factory = SolverFactory::<MockBridge>::create(
252            config,
253            "http://localhost:8080",
254            create_test_domain(),
255            create_test_constraints(),
256            "AGFzbQ==".to_string(),
257        );
258
259        assert_eq!(factory.config().random_seed, Some(42));
260    }
261
262    #[test]
263    fn test_solver_bridge_access() {
264        let factory = SolverFactory::create(
265            SolverConfig::new(),
266            "http://localhost:8080",
267            create_test_domain(),
268            create_test_constraints(),
269            "AGFzbQ==".to_string(),
270        );
271
272        let bridge = Arc::new(MockBridge::new());
273        let solver = factory.build_solver(bridge.clone());
274
275        assert!(Arc::ptr_eq(solver.bridge(), &bridge));
276    }
277}