Skip to main content

solverforge_solver/descriptor_standard/
bindings.rs

1use 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}