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
9#[derive(Clone)]
10pub(crate) struct VariableBinding {
11 pub(crate) descriptor_index: usize,
12 pub(crate) entity_type_name: &'static str,
13 pub(crate) variable_name: &'static str,
14 pub(crate) getter: UsizeGetter,
15 pub(crate) setter: UsizeSetter,
16 pub(crate) value_range_provider: Option<&'static str>,
17 pub(crate) provider: Option<UsizeEntityValueProvider>,
18 pub(crate) range_type: ValueRangeType,
19}
20
21impl Debug for VariableBinding {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 f.debug_struct("VariableBinding")
24 .field("descriptor_index", &self.descriptor_index)
25 .field("entity_type_name", &self.entity_type_name)
26 .field("variable_name", &self.variable_name)
27 .field("range_type", &self.range_type)
28 .finish()
29 }
30}
31
32impl VariableBinding {
33 pub(crate) fn values_for_entity(
34 &self,
35 solution_descriptor: &SolutionDescriptor,
36 solution: &dyn Any,
37 entity: &dyn Any,
38 ) -> Vec<usize> {
39 match (&self.provider, &self.range_type) {
40 (Some(provider), _) => provider(entity),
41 (_, ValueRangeType::CountableRange { from, to }) => {
42 let start = *from;
43 let end = *to;
44 (start..end)
45 .filter_map(|value| usize::try_from(value).ok())
46 .collect()
47 }
48 _ => self
49 .value_range_provider
50 .and_then(|provider_name| {
51 solution_descriptor
52 .problem_fact_descriptors
53 .iter()
54 .find(|descriptor| descriptor.solution_field == provider_name)
55 .and_then(|descriptor| descriptor.extractor.as_ref())
56 .and_then(|extractor| extractor.count(solution))
57 .or_else(|| {
58 solution_descriptor
59 .entity_descriptors
60 .iter()
61 .find(|descriptor| descriptor.solution_field == provider_name)
62 .and_then(|descriptor| descriptor.extractor.as_ref())
63 .and_then(|extractor| extractor.count(solution))
64 })
65 })
66 .map(|count| (0..count).collect())
67 .unwrap_or_default(),
68 }
69 }
70}
71
72pub(crate) fn collect_bindings(descriptor: &SolutionDescriptor) -> Vec<VariableBinding> {
73 let mut bindings = Vec::new();
74 for (descriptor_index, entity_descriptor) in descriptor.entity_descriptors.iter().enumerate() {
75 for variable in entity_descriptor.genuine_variable_descriptors() {
76 let Some(getter) = variable.usize_getter else {
77 continue;
78 };
79 let Some(setter) = variable.usize_setter else {
80 continue;
81 };
82 bindings.push(VariableBinding {
83 descriptor_index,
84 entity_type_name: entity_descriptor.type_name,
85 variable_name: variable.name,
86 getter,
87 setter,
88 value_range_provider: variable.value_range_provider,
89 provider: variable.entity_value_provider,
90 range_type: variable.value_range_type.clone(),
91 });
92 }
93 }
94 bindings
95}
96
97pub(crate) fn find_binding(
98 bindings: &[VariableBinding],
99 entity_class: Option<&str>,
100 variable_name: Option<&str>,
101) -> Vec<VariableBinding> {
102 bindings
103 .iter()
104 .filter(|binding| entity_class.is_none_or(|name| name == binding.entity_type_name))
105 .filter(|binding| variable_name.is_none_or(|name| name == binding.variable_name))
106 .cloned()
107 .collect()
108}
109
110pub fn descriptor_has_bindings(descriptor: &SolutionDescriptor) -> bool {
111 !collect_bindings(descriptor).is_empty()
112}
113
114pub fn standard_work_remaining<S>(
115 descriptor: &SolutionDescriptor,
116 entity_class: Option<&str>,
117 variable_name: Option<&str>,
118 solution: &S,
119) -> bool
120where
121 S: PlanningSolution + 'static,
122{
123 let bindings = find_binding(&collect_bindings(descriptor), entity_class, variable_name);
124 for binding in bindings {
125 let Some(entity_count) = descriptor
126 .entity_descriptors
127 .get(binding.descriptor_index)
128 .and_then(|entity| entity.entity_count(solution as &dyn Any))
129 else {
130 continue;
131 };
132 for entity_index in 0..entity_count {
133 let entity = descriptor
134 .get_entity(solution as &dyn Any, binding.descriptor_index, entity_index)
135 .expect("entity lookup failed while checking standard work");
136 if (binding.getter)(entity).is_none()
137 && !binding
138 .values_for_entity(descriptor, solution as &dyn Any, entity)
139 .is_empty()
140 {
141 return true;
142 }
143 }
144 }
145 false
146}
147
148pub fn standard_target_matches(
149 descriptor: &SolutionDescriptor,
150 entity_class: Option<&str>,
151 variable_name: Option<&str>,
152) -> bool {
153 !find_binding(&collect_bindings(descriptor), entity_class, variable_name).is_empty()
154}