solverforge_solver/builder/context/
model.rs1use 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}