1use std::fmt;
4use std::sync::Arc;
5
6use crate::domain::{EntityClassId, SolutionDescriptor, VariableId};
7use crate::score::Score;
8
9mod backend;
10mod resolution;
11
12use backend::{BackendListAccess, BackendScalarAccess};
13use resolution::{resolve_dynamic_descriptor_index, DynamicVariableKind};
14
15pub trait DynamicModelBackend: Clone + Send + Sync + 'static {
21 type Score: Score;
22
23 fn entity_count(&self, entity: EntityClassId) -> usize;
24
25 fn get_scalar(&self, entity: EntityClassId, row: usize, variable: VariableId) -> Option<usize>;
26
27 fn set_scalar(
28 &mut self,
29 entity: EntityClassId,
30 row: usize,
31 variable: VariableId,
32 value: Option<usize>,
33 );
34
35 fn list_len(&self, entity: EntityClassId, row: usize, variable: VariableId) -> usize;
36
37 fn list_get(
38 &self,
39 entity: EntityClassId,
40 row: usize,
41 variable: VariableId,
42 pos: usize,
43 ) -> Option<usize>;
44
45 fn list_insert(
46 &mut self,
47 entity: EntityClassId,
48 row: usize,
49 variable: VariableId,
50 pos: usize,
51 value: usize,
52 );
53
54 fn list_remove(
55 &mut self,
56 entity: EntityClassId,
57 row: usize,
58 variable: VariableId,
59 pos: usize,
60 ) -> Option<usize>;
61
62 fn candidate_values(&self, entity: EntityClassId, row: usize, variable: VariableId)
63 -> &[usize];
64
65 fn list_element_count(&self, _entity: EntityClassId, _variable: VariableId) -> usize {
66 0
67 }
68
69 fn list_element(
70 &self,
71 _entity: EntityClassId,
72 _variable: VariableId,
73 element_index: usize,
74 ) -> Option<usize> {
75 Some(element_index)
76 }
77
78 fn list_assigned_elements(&self, _entity: EntityClassId, _variable: VariableId) -> Vec<usize> {
79 Vec::new()
80 }
81}
82
83pub trait DynamicScalarAccess<S>: Send + Sync
85where
86 S: Clone + Send + Sync + 'static,
87{
88 fn entity_class(&self) -> EntityClassId;
89 fn variable(&self) -> VariableId;
90 fn entity_count(&self, solution: &S) -> usize;
91 fn get(&self, solution: &S, row: usize) -> Option<usize>;
92 fn set(&self, solution: &mut S, row: usize, value: Option<usize>);
93 fn candidate_values<'a>(&self, solution: &'a S, row: usize) -> &'a [usize];
94}
95
96pub trait DynamicListAccess<S>: Send + Sync
98where
99 S: Clone + Send + Sync + 'static,
100{
101 fn entity_class(&self) -> EntityClassId;
102 fn variable(&self) -> VariableId;
103 fn entity_count(&self, solution: &S) -> usize;
104 fn element_count(&self, solution: &S) -> usize;
105 fn element(&self, solution: &S, element_index: usize) -> Option<usize>;
106 fn assigned_elements(&self, solution: &S) -> Vec<usize>;
107 fn len(&self, solution: &S, row: usize) -> usize;
108 fn get(&self, solution: &S, row: usize, pos: usize) -> Option<usize>;
109 fn insert(&self, solution: &mut S, row: usize, pos: usize, value: usize);
110 fn remove(&self, solution: &mut S, row: usize, pos: usize) -> Option<usize>;
111}
112
113pub struct DynamicScalarVariableSlot<S> {
115 pub entity: EntityClassId,
116 pub variable: VariableId,
117 pub entity_type_name: &'static str,
118 pub variable_name: &'static str,
119 pub allows_unassigned: bool,
120 descriptor_index: Option<usize>,
121 access: Arc<dyn DynamicScalarAccess<S>>,
122}
123
124impl<S> Clone for DynamicScalarVariableSlot<S> {
125 fn clone(&self) -> Self {
126 Self {
127 entity: self.entity,
128 variable: self.variable,
129 entity_type_name: self.entity_type_name,
130 variable_name: self.variable_name,
131 allows_unassigned: self.allows_unassigned,
132 descriptor_index: self.descriptor_index,
133 access: Arc::clone(&self.access),
134 }
135 }
136}
137
138impl<S> fmt::Debug for DynamicScalarVariableSlot<S> {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 f.debug_struct("DynamicScalarVariableSlot")
141 .field("entity", &self.entity)
142 .field("variable", &self.variable)
143 .field("entity_type_name", &self.entity_type_name)
144 .field("variable_name", &self.variable_name)
145 .field("allows_unassigned", &self.allows_unassigned)
146 .field("descriptor_index", &self.descriptor_index)
147 .finish()
148 }
149}
150
151impl<S> PartialEq for DynamicScalarVariableSlot<S> {
152 fn eq(&self, other: &Self) -> bool {
153 self.entity == other.entity
154 && self.variable == other.variable
155 && self.entity_type_name == other.entity_type_name
156 && self.variable_name == other.variable_name
157 && self.allows_unassigned == other.allows_unassigned
158 && self.descriptor_index == other.descriptor_index
159 }
160}
161
162impl<S> Eq for DynamicScalarVariableSlot<S> {}
163
164impl<S> DynamicScalarVariableSlot<S> {
165 pub fn with_access(
166 entity: EntityClassId,
167 variable: VariableId,
168 entity_type_name: &'static str,
169 variable_name: &'static str,
170 allows_unassigned: bool,
171 access: Arc<dyn DynamicScalarAccess<S>>,
172 ) -> Self {
173 Self {
174 entity,
175 variable,
176 entity_type_name,
177 variable_name,
178 allows_unassigned,
179 descriptor_index: None,
180 access,
181 }
182 }
183
184 pub fn with_descriptor_index(mut self, descriptor_index: usize) -> Self {
185 self.descriptor_index = Some(descriptor_index);
186 self
187 }
188
189 pub fn resolve_descriptor_index(
190 &mut self,
191 descriptor: &SolutionDescriptor,
192 ) -> Result<(), String> {
193 let descriptor_index = resolve_dynamic_descriptor_index(
194 descriptor,
195 self.entity,
196 self.variable,
197 self.entity_type_name,
198 self.variable_name,
199 DynamicVariableKind::Scalar,
200 )?;
201 if let Some(existing) = self.descriptor_index {
202 if existing != descriptor_index {
203 return Err(format!(
204 "dynamic scalar variable {}.{} was pre-bound to descriptor index {existing}, but logical entity ID {} resolves to descriptor index {descriptor_index}",
205 self.entity_type_name, self.variable_name, self.entity.0
206 ));
207 }
208 }
209 self.descriptor_index = Some(descriptor_index);
210 Ok(())
211 }
212
213 pub fn resolved_against(mut self, descriptor: &SolutionDescriptor) -> Result<Self, String> {
214 self.resolve_descriptor_index(descriptor)?;
215 Ok(self)
216 }
217
218 pub fn is_descriptor_resolved(&self) -> bool {
219 self.descriptor_index.is_some()
220 }
221
222 pub fn descriptor_index(&self) -> usize {
223 self.descriptor_index.unwrap_or_else(|| {
224 panic!(
225 "dynamic scalar variable {}.{} has not been resolved against a SolutionDescriptor",
226 self.entity_type_name, self.variable_name
227 )
228 })
229 }
230
231 pub fn matches_target(&self, entity_class: Option<&str>, variable_name: Option<&str>) -> bool {
232 entity_class.is_none_or(|entity| entity == self.entity_type_name)
233 && variable_name.is_none_or(|variable| variable == self.variable_name)
234 }
235
236 pub fn entity_count(&self, solution: &S) -> usize
237 where
238 S: Clone + Send + Sync + 'static,
239 {
240 self.access.entity_count(solution)
241 }
242
243 pub fn current_value(&self, solution: &S, row: usize) -> Option<usize>
244 where
245 S: Clone + Send + Sync + 'static,
246 {
247 self.access.get(solution, row)
248 }
249
250 pub fn set_value(&self, solution: &mut S, row: usize, value: Option<usize>)
251 where
252 S: Clone + Send + Sync + 'static,
253 {
254 self.access.set(solution, row, value);
255 }
256
257 pub fn candidate_values<'a>(&self, solution: &'a S, row: usize) -> &'a [usize]
258 where
259 S: Clone + Send + Sync + 'static,
260 {
261 self.access.candidate_values(solution, row)
262 }
263
264 pub fn value_is_legal(&self, solution: &S, row: usize, value: Option<usize>) -> bool
265 where
266 S: Clone + Send + Sync + 'static,
267 {
268 let Some(value) = value else {
269 return self.allows_unassigned;
270 };
271 self.candidate_values(solution, row).contains(&value)
272 }
273}
274
275impl<S> DynamicScalarVariableSlot<S>
276where
277 S: DynamicModelBackend,
278{
279 pub fn new(
280 entity: EntityClassId,
281 variable: VariableId,
282 entity_type_name: &'static str,
283 variable_name: &'static str,
284 allows_unassigned: bool,
285 ) -> Self {
286 Self::with_access(
287 entity,
288 variable,
289 entity_type_name,
290 variable_name,
291 allows_unassigned,
292 Arc::new(BackendScalarAccess { entity, variable }),
293 )
294 }
295}
296
297pub struct DynamicListVariableSlot<S> {
299 pub entity: EntityClassId,
300 pub variable: VariableId,
301 pub entity_type_name: &'static str,
302 pub variable_name: &'static str,
303 descriptor_index: Option<usize>,
304 access: Arc<dyn DynamicListAccess<S>>,
305}
306
307impl<S> Clone for DynamicListVariableSlot<S> {
308 fn clone(&self) -> Self {
309 Self {
310 entity: self.entity,
311 variable: self.variable,
312 entity_type_name: self.entity_type_name,
313 variable_name: self.variable_name,
314 descriptor_index: self.descriptor_index,
315 access: Arc::clone(&self.access),
316 }
317 }
318}
319
320impl<S> fmt::Debug for DynamicListVariableSlot<S> {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322 f.debug_struct("DynamicListVariableSlot")
323 .field("entity", &self.entity)
324 .field("variable", &self.variable)
325 .field("entity_type_name", &self.entity_type_name)
326 .field("variable_name", &self.variable_name)
327 .field("descriptor_index", &self.descriptor_index)
328 .finish()
329 }
330}
331
332impl<S> PartialEq for DynamicListVariableSlot<S> {
333 fn eq(&self, other: &Self) -> bool {
334 self.entity == other.entity
335 && self.variable == other.variable
336 && self.entity_type_name == other.entity_type_name
337 && self.variable_name == other.variable_name
338 && self.descriptor_index == other.descriptor_index
339 }
340}
341
342impl<S> Eq for DynamicListVariableSlot<S> {}
343
344impl<S> DynamicListVariableSlot<S> {
345 pub fn with_access(
346 entity: EntityClassId,
347 variable: VariableId,
348 entity_type_name: &'static str,
349 variable_name: &'static str,
350 access: Arc<dyn DynamicListAccess<S>>,
351 ) -> Self {
352 Self {
353 entity,
354 variable,
355 entity_type_name,
356 variable_name,
357 descriptor_index: None,
358 access,
359 }
360 }
361
362 pub fn with_descriptor_index(mut self, descriptor_index: usize) -> Self {
363 self.descriptor_index = Some(descriptor_index);
364 self
365 }
366
367 pub fn resolve_descriptor_index(
368 &mut self,
369 descriptor: &SolutionDescriptor,
370 ) -> Result<(), String> {
371 let descriptor_index = resolve_dynamic_descriptor_index(
372 descriptor,
373 self.entity,
374 self.variable,
375 self.entity_type_name,
376 self.variable_name,
377 DynamicVariableKind::List,
378 )?;
379 if let Some(existing) = self.descriptor_index {
380 if existing != descriptor_index {
381 return Err(format!(
382 "dynamic list variable {}.{} was pre-bound to descriptor index {existing}, but logical entity ID {} resolves to descriptor index {descriptor_index}",
383 self.entity_type_name, self.variable_name, self.entity.0
384 ));
385 }
386 }
387 self.descriptor_index = Some(descriptor_index);
388 Ok(())
389 }
390
391 pub fn resolved_against(mut self, descriptor: &SolutionDescriptor) -> Result<Self, String> {
392 self.resolve_descriptor_index(descriptor)?;
393 Ok(self)
394 }
395
396 pub fn is_descriptor_resolved(&self) -> bool {
397 self.descriptor_index.is_some()
398 }
399
400 pub fn descriptor_index(&self) -> usize {
401 self.descriptor_index.unwrap_or_else(|| {
402 panic!(
403 "dynamic list variable {}.{} has not been resolved against a SolutionDescriptor",
404 self.entity_type_name, self.variable_name
405 )
406 })
407 }
408
409 pub fn matches_target(&self, entity_class: Option<&str>, variable_name: Option<&str>) -> bool {
410 entity_class.is_none_or(|entity| entity == self.entity_type_name)
411 && variable_name.is_none_or(|variable| variable == self.variable_name)
412 }
413
414 pub fn entity_count(&self, solution: &S) -> usize
415 where
416 S: Clone + Send + Sync + 'static,
417 {
418 self.access.entity_count(solution)
419 }
420
421 pub fn element_count(&self, solution: &S) -> usize
422 where
423 S: Clone + Send + Sync + 'static,
424 {
425 self.access.element_count(solution)
426 }
427
428 pub fn element(&self, solution: &S, element_index: usize) -> Option<usize>
429 where
430 S: Clone + Send + Sync + 'static,
431 {
432 self.access.element(solution, element_index)
433 }
434
435 pub fn assigned_elements(&self, solution: &S) -> Vec<usize>
436 where
437 S: Clone + Send + Sync + 'static,
438 {
439 self.access.assigned_elements(solution)
440 }
441
442 pub fn list_len(&self, solution: &S, row: usize) -> usize
443 where
444 S: Clone + Send + Sync + 'static,
445 {
446 self.access.len(solution, row)
447 }
448
449 pub fn list_get(&self, solution: &S, row: usize, pos: usize) -> Option<usize>
450 where
451 S: Clone + Send + Sync + 'static,
452 {
453 self.access.get(solution, row, pos)
454 }
455
456 pub fn list_insert(&self, solution: &mut S, row: usize, pos: usize, value: usize)
457 where
458 S: Clone + Send + Sync + 'static,
459 {
460 self.access.insert(solution, row, pos, value);
461 }
462
463 pub fn list_remove(&self, solution: &mut S, row: usize, pos: usize) -> Option<usize>
464 where
465 S: Clone + Send + Sync + 'static,
466 {
467 self.access.remove(solution, row, pos)
468 }
469}
470
471impl<S> DynamicListVariableSlot<S>
472where
473 S: DynamicModelBackend,
474{
475 pub fn new(
476 entity: EntityClassId,
477 variable: VariableId,
478 entity_type_name: &'static str,
479 variable_name: &'static str,
480 ) -> Self {
481 Self::with_access(
482 entity,
483 variable,
484 entity_type_name,
485 variable_name,
486 Arc::new(BackendListAccess { entity, variable }),
487 )
488 }
489}