Skip to main content

solverforge_scoring/api/
weight_overrides.rs

1// Runtime constraint weight configuration.
2//
3// Allows dynamic adjustment of constraint weights without recompiling.
4
5use std::collections::HashMap;
6use std::fmt::Debug;
7use std::sync::Arc;
8
9use solverforge_core::Score;
10
11// Holds runtime overrides for constraint weights.
12//
13// Use this to adjust constraint weights without recompiling. Weights can be
14// changed between solver runs or even during solving (if you rebuild constraints).
15#[derive(Clone)]
16pub struct ConstraintWeightOverrides<Sc: Score> {
17    weights: HashMap<String, Sc>,
18}
19
20impl<Sc: Score> Debug for ConstraintWeightOverrides<Sc> {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        f.debug_struct("ConstraintWeightOverrides")
23            .field("count", &self.weights.len())
24            .finish()
25    }
26}
27
28impl<Sc: Score> Default for ConstraintWeightOverrides<Sc> {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl<Sc: Score> ConstraintWeightOverrides<Sc> {
35    // Creates an empty overrides container.
36    pub fn new() -> Self {
37        Self {
38            weights: HashMap::new(),
39        }
40    }
41
42    // Creates overrides from an iterator of (name, weight) pairs.
43    pub fn from_pairs<I, N>(iter: I) -> Self
44    where
45        I: IntoIterator<Item = (N, Sc)>,
46        N: Into<String>,
47    {
48        let weights = iter.into_iter().map(|(n, w)| (n.into(), w)).collect();
49        Self { weights }
50    }
51
52    // Sets the weight for a constraint.
53    pub fn put<N: Into<String>>(&mut self, name: N, weight: Sc) {
54        self.weights.insert(name.into(), weight);
55    }
56
57    // Removes the override for a constraint.
58    pub fn remove(&mut self, name: &str) -> Option<Sc> {
59        self.weights.remove(name)
60    }
61
62    // Gets the overridden weight, or returns the default if not overridden.
63    pub fn get_or_default(&self, name: &str, default: Sc) -> Sc {
64        self.weights.get(name).cloned().unwrap_or(default)
65    }
66
67    // Gets the overridden weight if present.
68    pub fn get(&self, name: &str) -> Option<&Sc> {
69        self.weights.get(name)
70    }
71
72    // Returns true if this constraint has an override.
73    pub fn contains(&self, name: &str) -> bool {
74        self.weights.contains_key(name)
75    }
76
77    // Returns the number of overrides.
78    pub fn len(&self) -> usize {
79        self.weights.len()
80    }
81
82    // Returns true if there are no overrides.
83    pub fn is_empty(&self) -> bool {
84        self.weights.is_empty()
85    }
86
87    // Clears all overrides.
88    pub fn clear(&mut self) {
89        self.weights.clear();
90    }
91
92    // Creates an Arc-wrapped version for sharing across threads.
93    pub fn into_arc(self) -> Arc<Self> {
94        Arc::new(self)
95    }
96}
97
98// Helper trait for creating weight functions from overrides.
99// This enables zero-erasure constraint building with runtime weight lookup.
100pub trait WeightProvider<Sc: Score>: Send + Sync {
101    // Gets the weight for a constraint by name.
102    fn weight(&self, name: &str) -> Option<Sc>;
103
104    // Gets the weight or returns the default.
105    fn weight_or_default(&self, name: &str, default: Sc) -> Sc {
106        self.weight(name).unwrap_or(default)
107    }
108}
109
110impl<Sc: Score> WeightProvider<Sc> for ConstraintWeightOverrides<Sc> {
111    fn weight(&self, name: &str) -> Option<Sc> {
112        self.get(name).cloned()
113    }
114}
115
116impl<Sc: Score> WeightProvider<Sc> for Arc<ConstraintWeightOverrides<Sc>> {
117    fn weight(&self, name: &str) -> Option<Sc> {
118        self.get(name).cloned()
119    }
120}