solverforge_solver/descriptor_standard/
bindings.rs1use std::any::Any;
2use std::fmt::{self, Debug};
3
4use solverforge_core::domain::{
5 PlanningSolution, SolutionDescriptor, UsizeEntityValueProvider, UsizeGetter, UsizeSetter,
6 ValueRangeType,
7};
8
9use crate::phase::construction::{ConstructionFrontier, ConstructionSlotId};
10
11#[derive(Clone)]
12pub(crate) struct VariableBinding {
13 pub(crate) binding_index: usize,
14 pub(crate) descriptor_index: usize,
15 pub(crate) entity_type_name: &'static str,
16 pub(crate) variable_name: &'static str,
17 pub(crate) allows_unassigned: bool,
18 pub(crate) getter: UsizeGetter,
19 pub(crate) setter: UsizeSetter,
20 pub(crate) value_range_provider: Option<&'static str>,
21 pub(crate) provider: Option<UsizeEntityValueProvider>,
22 pub(crate) range_type: ValueRangeType,
23}
24
25impl Debug for VariableBinding {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 f.debug_struct("VariableBinding")
28 .field("binding_index", &self.binding_index)
29 .field("descriptor_index", &self.descriptor_index)
30 .field("entity_type_name", &self.entity_type_name)
31 .field("variable_name", &self.variable_name)
32 .field("allows_unassigned", &self.allows_unassigned)
33 .field("range_type", &self.range_type)
34 .finish()
35 }
36}
37
38impl VariableBinding {
39 pub(crate) fn slot_id(&self, entity_index: usize) -> ConstructionSlotId {
40 ConstructionSlotId::new(self.binding_index, entity_index)
41 }
42
43 pub(crate) fn values_for_entity(
44 &self,
45 solution_descriptor: &SolutionDescriptor,
46 solution: &dyn Any,
47 entity: &dyn Any,
48 ) -> Vec<usize> {
49 match (&self.provider, &self.range_type) {
50 (Some(provider), _) => provider(entity),
51 (_, ValueRangeType::CountableRange { from, to }) => {
52 let start = *from;
53 let end = *to;
54 (start..end)
55 .filter_map(|value| usize::try_from(value).ok())
56 .collect()
57 }
58 _ => self
59 .value_range_provider
60 .and_then(|provider_name| {
61 solution_descriptor
62 .problem_fact_descriptors
63 .iter()
64 .find(|descriptor| descriptor.solution_field == provider_name)
65 .and_then(|descriptor| descriptor.extractor.as_ref())
66 .and_then(|extractor| extractor.count(solution))
67 .or_else(|| {
68 solution_descriptor
69 .entity_descriptors
70 .iter()
71 .find(|descriptor| descriptor.solution_field == provider_name)
72 .and_then(|descriptor| descriptor.extractor.as_ref())
73 .and_then(|extractor| extractor.count(solution))
74 })
75 })
76 .map(|count| (0..count).collect())
77 .unwrap_or_default(),
78 }
79 }
80}
81
82pub(crate) fn collect_bindings(descriptor: &SolutionDescriptor) -> Vec<VariableBinding> {
83 let mut bindings = Vec::new();
84 for (descriptor_index, entity_descriptor) in descriptor.entity_descriptors.iter().enumerate() {
85 for variable in entity_descriptor.genuine_variable_descriptors() {
86 let Some(getter) = variable.usize_getter else {
87 continue;
88 };
89 let Some(setter) = variable.usize_setter else {
90 continue;
91 };
92 bindings.push(VariableBinding {
93 binding_index: bindings.len(),
94 descriptor_index,
95 entity_type_name: entity_descriptor.type_name,
96 variable_name: variable.name,
97 allows_unassigned: variable.allows_unassigned,
98 getter,
99 setter,
100 value_range_provider: variable.value_range_provider,
101 provider: variable.entity_value_provider,
102 range_type: variable.value_range_type.clone(),
103 });
104 }
105 }
106 bindings
107}
108
109pub(crate) fn find_binding(
110 bindings: &[VariableBinding],
111 entity_class: Option<&str>,
112 variable_name: Option<&str>,
113) -> Vec<VariableBinding> {
114 bindings
115 .iter()
116 .filter(|binding| entity_class.is_none_or(|name| name == binding.entity_type_name))
117 .filter(|binding| variable_name.is_none_or(|name| name == binding.variable_name))
118 .cloned()
119 .collect()
120}
121
122pub fn descriptor_has_bindings(descriptor: &SolutionDescriptor) -> bool {
123 !collect_bindings(descriptor).is_empty()
124}
125
126pub(crate) fn standard_work_remaining_with_frontier<S>(
127 descriptor: &SolutionDescriptor,
128 frontier: &ConstructionFrontier,
129 solution_revision: u64,
130 entity_class: Option<&str>,
131 variable_name: Option<&str>,
132 solution: &S,
133) -> bool
134where
135 S: PlanningSolution + 'static,
136{
137 let bindings = find_binding(&collect_bindings(descriptor), entity_class, variable_name);
138 for binding in bindings {
139 let Some(entity_count) = descriptor
140 .entity_descriptors
141 .get(binding.descriptor_index)
142 .and_then(|entity| entity.entity_count(solution as &dyn Any))
143 else {
144 continue;
145 };
146 for entity_index in 0..entity_count {
147 let entity = descriptor
148 .get_entity(solution as &dyn Any, binding.descriptor_index, entity_index)
149 .expect("entity lookup failed while checking standard work");
150 if (binding.getter)(entity).is_some()
151 || frontier.is_standard_completed(binding.slot_id(entity_index), solution_revision)
152 {
153 continue;
154 }
155 if !binding
156 .values_for_entity(descriptor, solution as &dyn Any, entity)
157 .is_empty()
158 {
159 return true;
160 }
161 }
162 }
163 false
164}
165
166pub fn standard_work_remaining<S>(
167 descriptor: &SolutionDescriptor,
168 entity_class: Option<&str>,
169 variable_name: Option<&str>,
170 solution: &S,
171) -> bool
172where
173 S: PlanningSolution + 'static,
174{
175 let bindings = find_binding(&collect_bindings(descriptor), entity_class, variable_name);
176 for binding in bindings {
177 let Some(entity_count) = descriptor
178 .entity_descriptors
179 .get(binding.descriptor_index)
180 .and_then(|entity| entity.entity_count(solution as &dyn Any))
181 else {
182 continue;
183 };
184 for entity_index in 0..entity_count {
185 let entity = descriptor
186 .get_entity(solution as &dyn Any, binding.descriptor_index, entity_index)
187 .expect("entity lookup failed while checking standard work");
188 if (binding.getter)(entity).is_none()
189 && !binding
190 .values_for_entity(descriptor, solution as &dyn Any, entity)
191 .is_empty()
192 {
193 return true;
194 }
195 }
196 }
197 false
198}
199
200pub fn standard_target_matches(
201 descriptor: &SolutionDescriptor,
202 entity_class: Option<&str>,
203 variable_name: Option<&str>,
204) -> bool {
205 !find_binding(&collect_bindings(descriptor), entity_class, variable_name).is_empty()
206}