Skip to main content

solverforge_solver/builder/context/
model.rs

1use std::fmt;
2use std::marker::PhantomData;
3
4use super::{ConflictRepair, ListVariableSlot, ScalarGroupBinding, ScalarVariableSlot};
5
6pub enum VariableSlot<S, V, DM, IDM> {
7    Scalar(ScalarVariableSlot<S>),
8    List(ListVariableSlot<S, V, DM, IDM>),
9}
10
11impl<S, V, DM: Clone, IDM: Clone> Clone for VariableSlot<S, V, DM, IDM> {
12    fn clone(&self) -> Self {
13        match self {
14            Self::Scalar(variable) => Self::Scalar(*variable),
15            Self::List(variable) => Self::List(variable.clone()),
16        }
17    }
18}
19
20impl<S, V, DM: fmt::Debug, IDM: fmt::Debug> fmt::Debug for VariableSlot<S, V, DM, IDM> {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        match self {
23            Self::Scalar(variable) => variable.fmt(f),
24            Self::List(variable) => variable.fmt(f),
25        }
26    }
27}
28
29pub struct RuntimeModel<S, V, DM, IDM> {
30    variables: Vec<VariableSlot<S, V, DM, IDM>>,
31    scalar_groups: Vec<ScalarGroupBinding<S>>,
32    conflict_repairs: Vec<ConflictRepair<S>>,
33    _phantom: PhantomData<(fn() -> S, fn() -> V)>,
34}
35
36impl<S, V, DM: Clone, IDM: Clone> Clone for RuntimeModel<S, V, DM, IDM> {
37    fn clone(&self) -> Self {
38        Self {
39            variables: self.variables.clone(),
40            scalar_groups: self.scalar_groups.clone(),
41            conflict_repairs: self.conflict_repairs.clone(),
42            _phantom: PhantomData,
43        }
44    }
45}
46
47impl<S, V, DM, IDM> RuntimeModel<S, V, DM, IDM> {
48    pub fn new(variables: Vec<VariableSlot<S, V, DM, IDM>>) -> Self {
49        Self {
50            variables,
51            scalar_groups: Vec::new(),
52            conflict_repairs: Vec::new(),
53            _phantom: PhantomData,
54        }
55    }
56
57    pub fn with_scalar_groups(mut self, groups: Vec<ScalarGroupBinding<S>>) -> Self {
58        self.scalar_groups = groups;
59        self
60    }
61
62    pub fn with_conflict_repairs(mut self, repairs: Vec<ConflictRepair<S>>) -> Self {
63        self.conflict_repairs = repairs;
64        self
65    }
66
67    pub fn variables(&self) -> &[VariableSlot<S, V, DM, IDM>] {
68        &self.variables
69    }
70
71    pub fn scalar_groups(&self) -> &[ScalarGroupBinding<S>] {
72        &self.scalar_groups
73    }
74
75    pub fn conflict_repairs(&self) -> &[ConflictRepair<S>] {
76        &self.conflict_repairs
77    }
78
79    pub fn is_empty(&self) -> bool {
80        self.variables.is_empty()
81    }
82
83    pub fn has_list_variables(&self) -> bool {
84        self.variables
85            .iter()
86            .any(|variable| matches!(variable, VariableSlot::List(_)))
87    }
88
89    pub fn has_scalar_variables(&self) -> bool {
90        self.variables
91            .iter()
92            .any(|variable| matches!(variable, VariableSlot::Scalar(_)))
93    }
94
95    pub fn is_scalar_only(&self) -> bool {
96        self.has_scalar_variables() && !self.has_list_variables()
97    }
98
99    pub fn has_nearby_scalar_change_variables(&self) -> bool {
100        self.scalar_variables()
101            .any(ScalarVariableSlot::supports_nearby_change)
102    }
103
104    pub fn has_nearby_scalar_swap_variables(&self) -> bool {
105        self.scalar_variables()
106            .any(ScalarVariableSlot::supports_nearby_swap)
107    }
108
109    pub fn assignment_scalar_groups(
110        &self,
111    ) -> impl Iterator<Item = (usize, &ScalarGroupBinding<S>)> {
112        self.scalar_groups
113            .iter()
114            .enumerate()
115            .filter(|(_, group)| group.is_assignment())
116    }
117
118    pub fn assignment_group_covers_scalar_variable(
119        &self,
120        variable: &ScalarVariableSlot<S>,
121    ) -> bool {
122        self.assignment_scalar_groups().any(|(_, group)| {
123            group.members.iter().any(|member| {
124                member.descriptor_index == variable.descriptor_index
125                    && member.variable_index == variable.variable_index
126            })
127        })
128    }
129
130    pub fn has_scalar_groups(&self) -> bool {
131        !self.scalar_groups.is_empty()
132    }
133
134    pub fn has_assignment_scalar_groups(&self) -> bool {
135        self.assignment_scalar_groups().next().is_some()
136    }
137
138    pub fn candidate_scalar_groups(&self) -> impl Iterator<Item = (usize, &ScalarGroupBinding<S>)> {
139        self.scalar_groups
140            .iter()
141            .enumerate()
142            .filter(|(_, group)| group.is_candidate_group())
143    }
144
145    pub fn has_candidate_scalar_groups(&self) -> bool {
146        self.candidate_scalar_groups().next().is_some()
147    }
148
149    pub fn has_list_ruin_variables(&self) -> bool {
150        self.list_variables().any(ListVariableSlot::supports_ruin)
151    }
152
153    pub fn has_k_opt_variables(&self) -> bool {
154        self.list_variables().any(ListVariableSlot::supports_k_opt)
155    }
156
157    pub fn has_conflict_repairs(&self) -> bool {
158        !self.conflict_repairs.is_empty()
159    }
160
161    pub fn scalar_variables(&self) -> impl Iterator<Item = &ScalarVariableSlot<S>> {
162        self.variables.iter().filter_map(|variable| match variable {
163            VariableSlot::Scalar(ctx) => Some(ctx),
164            VariableSlot::List(_) => None,
165        })
166    }
167
168    pub fn scalar_variable_target(
169        &self,
170        entity_class: Option<&str>,
171        variable_name: Option<&str>,
172    ) -> Result<ScalarVariableSlot<S>, String> {
173        let mut matches = self
174            .scalar_variables()
175            .filter(|slot| slot.matches_target(entity_class, variable_name));
176        let Some(first) = matches.next().copied() else {
177            return Err(match (entity_class, variable_name) {
178                (Some(entity), Some(variable)) => {
179                    format!("no scalar variable `{entity}.{variable}` exists in the runtime model")
180                }
181                (Some(entity), None) => {
182                    format!("no scalar variable for entity `{entity}` exists in the runtime model")
183                }
184                (None, Some(variable)) => {
185                    format!("no scalar variable named `{variable}` exists in the runtime model")
186                }
187                (None, None) => {
188                    "exhaustive search requires exactly one scalar variable or an explicit target"
189                        .to_string()
190                }
191            });
192        };
193        if matches.next().is_some() {
194            return Err(
195                "exhaustive search target is ambiguous; specify entity_class and variable_name"
196                    .to_string(),
197            );
198        }
199        Ok(first)
200    }
201
202    pub fn finite_scalar_candidate_space_estimate(
203        &self,
204        solution: &S,
205        slot: ScalarVariableSlot<S>,
206        value_candidate_limit: Option<usize>,
207    ) -> Option<usize> {
208        let entity_count = (slot.entity_count)(solution);
209        let mut total: usize = 1;
210        for entity_index in 0..entity_count {
211            let candidate_count = slot
212                .candidate_values_for_entity(solution, entity_index, value_candidate_limit)
213                .len();
214            if candidate_count == 0 {
215                return Some(0);
216            }
217            total = total.checked_mul(candidate_count)?;
218        }
219        Some(total)
220    }
221
222    pub fn list_variables(&self) -> impl Iterator<Item = &ListVariableSlot<S, V, DM, IDM>> {
223        self.variables.iter().filter_map(|variable| match variable {
224            VariableSlot::List(ctx) => Some(ctx),
225            VariableSlot::Scalar(_) => None,
226        })
227    }
228}
229
230impl<S, V, DM: fmt::Debug, IDM: fmt::Debug> fmt::Debug for RuntimeModel<S, V, DM, IDM> {
231    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232        f.debug_struct("RuntimeModel")
233            .field("variables", &self.variables)
234            .field("scalar_groups", &self.scalar_groups)
235            .field("conflict_repairs", &self.conflict_repairs)
236            .finish()
237    }
238}