Skip to main content

solverforge_solver/builder/context/
model_resolution.rs

1//! Descriptor-bound runtime-model resolution and provider-registry freezing.
2
3use std::collections::{BTreeMap, HashSet};
4
5use solverforge_core::domain::{DynamicScalarVariableSlot, SolutionDescriptor};
6
7use crate::descriptor::collect_bindings;
8
9use super::{RuntimeModel, RuntimeScalarSlot, VariableSlot};
10
11impl<S, V, DM, IDM> RuntimeModel<S, V, DM, IDM> {
12    pub fn resolve_dynamic_descriptor_indexes(
13        mut self,
14        descriptor: &SolutionDescriptor,
15    ) -> Result<Self, String>
16    where
17        S: 'static,
18    {
19        for variable in &mut self.variables {
20            match variable {
21                VariableSlot::DynamicScalar(slot) => slot.resolve_descriptor_index(descriptor)?,
22                VariableSlot::DynamicList(slot) => slot.resolve_descriptor_index(descriptor)?,
23                VariableSlot::Scalar(_) | VariableSlot::List(_) => {}
24            }
25        }
26        let dynamic_scalar_slots = self.dynamic_scalar_variables().cloned().collect::<Vec<_>>();
27        self.canonicalize_dynamic_scalar_groups(&dynamic_scalar_slots)?;
28        let provider_scalar_slots = self
29            .variables
30            .iter()
31            .filter_map(|variable| match variable {
32                VariableSlot::Scalar(slot) => Some(RuntimeScalarSlot::Static(*slot)),
33                VariableSlot::DynamicScalar(slot) => Some(RuntimeScalarSlot::Dynamic(slot.clone())),
34                VariableSlot::List(_) | VariableSlot::DynamicList(_) => None,
35            })
36            .collect::<Vec<_>>();
37        self.runtime_provider_registry.freeze(
38            &provider_scalar_slots,
39            &self.scalar_groups,
40            &self.conflict_repairs,
41        )?;
42        self.bind_scalar_group_construction_slots(descriptor)?;
43        Ok(self)
44    }
45
46    pub fn assert_dynamic_descriptor_indexes_resolved(&self) {
47        for variable in &self.variables {
48            match variable {
49                VariableSlot::DynamicScalar(slot) => {
50                    let _ = slot.descriptor_index();
51                    let _ = slot.descriptor_variable_index();
52                }
53                VariableSlot::DynamicList(slot) => {
54                    let _ = slot.descriptor_index();
55                    let _ = slot.descriptor_variable_index();
56                }
57                VariableSlot::Scalar(_) | VariableSlot::List(_) => {}
58            }
59        }
60        for group in &self.scalar_groups {
61            for member in &group.members {
62                if member.is_dynamic() {
63                    let _ = member.dynamic_identity();
64                    assert_ne!(
65                        member.descriptor_index,
66                        usize::MAX,
67                        "dynamic scalar group `{}` has an unresolved target descriptor",
68                        group.group_name
69                    );
70                    assert_ne!(
71                        member.variable_index,
72                        usize::MAX,
73                        "dynamic scalar group `{}` has an unresolved target variable",
74                        group.group_name
75                    );
76                }
77            }
78        }
79    }
80    fn canonicalize_dynamic_scalar_groups(
81        &mut self,
82        scalar_slots: &[DynamicScalarVariableSlot<S>],
83    ) -> Result<(), String> {
84        let mut group_names = HashSet::new();
85        let mut assignment_targets = HashSet::new();
86        for group in &mut self.scalar_groups {
87            if !group_names.insert(group.group_name) {
88                return Err(format!(
89                    "scalar group `{}` is registered more than once",
90                    group.group_name
91                ));
92            }
93            group.canonicalize_dynamic_members(scalar_slots)?;
94            if let Some(assignment) = group.assignment() {
95                let target = assignment.target();
96                if !assignment_targets.insert((target.descriptor_index, target.variable_index)) {
97                    return Err(format!(
98                        "assignment scalar target {}.{} is owned by more than one scalar group",
99                        target.entity_type_name, target.variable_name
100                    ));
101                }
102            }
103        }
104        Ok(())
105    }
106
107    fn bind_scalar_group_construction_slots(
108        &mut self,
109        descriptor: &SolutionDescriptor,
110    ) -> Result<(), String> {
111        let typed_binding_indexes = collect_bindings(descriptor)
112            .into_iter()
113            .map(|binding| {
114                (
115                    (binding.descriptor_index, binding.variable_index),
116                    binding.binding_index,
117                )
118            })
119            .collect::<BTreeMap<_, _>>();
120        let mut next_dynamic_binding_index = self.variables.len().max(
121            typed_binding_indexes
122                .values()
123                .copied()
124                .max()
125                .map_or(0, |index| index.saturating_add(1)),
126        );
127        let mut dynamic_binding_indexes = BTreeMap::new();
128
129        for group in &mut self.scalar_groups {
130            for member in &mut group.members {
131                let binding_index = if let Some(identity) = member.dynamic_identity() {
132                    *dynamic_binding_indexes.entry(identity).or_insert_with(|| {
133                        let binding_index = next_dynamic_binding_index;
134                        next_dynamic_binding_index = next_dynamic_binding_index
135                            .checked_add(1)
136                            .expect("dynamic scalar group construction slot index overflow");
137                        binding_index
138                    })
139                } else {
140                    *typed_binding_indexes
141                        .get(&(member.descriptor_index, member.variable_index))
142                        .ok_or_else(|| {
143                            format!(
144                                "scalar group `{}` target {}.{} has no descriptor construction binding",
145                                group.group_name, member.entity_type_name, member.variable_name
146                            )
147                        })?
148                };
149                member.set_construction_binding_index(binding_index);
150            }
151
152            let assignment_target = group.assignment().map(|assignment| {
153                (
154                    assignment.target().descriptor_index,
155                    assignment.target().variable_index,
156                )
157            });
158            let Some((descriptor_index, variable_index)) = assignment_target else {
159                continue;
160            };
161            let binding_index = group
162                .members
163                .iter()
164                .find(|member| {
165                    member.descriptor_index == descriptor_index
166                        && member.variable_index == variable_index
167                })
168                .map(|member| member.construction_binding_index())
169                .ok_or_else(|| {
170                    format!(
171                        "assignment scalar group `{}` target has no registered construction slot",
172                        group.group_name
173                    )
174                })?;
175            group
176                .assignment_mut()
177                .expect("assignment target must retain its scalar group binding")
178                .target_mut()
179                .set_construction_binding_index(binding_index);
180        }
181
182        Ok(())
183    }
184}