Skip to main content

solverforge_scoring/api/
weight_overrides.rs

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