Skip to main content

solverforge_solver/builder/context/
candidate_metric.rs

1use std::fmt;
2use std::sync::Arc;
3
4use crate::stats::CandidateTraceIdentity;
5
6/// Host-owned numeric metadata for one already-generated move.
7///
8/// The compiled selector invokes this only for `sorted` or `probabilistic`
9/// order. The callback sees the same canonical logical identity used by the
10/// candidate trace, so native and host-language models rank identical move
11/// coordinates without rebuilding or executing a second selector.
12pub trait RuntimeCandidateMetric<S>: Send + Sync {
13    fn measure(&self, solution: &S, candidate: &CandidateTraceIdentity) -> f64;
14}
15
16pub struct RuntimeCandidateMetricBinding<S> {
17    name: Arc<str>,
18    metric: Arc<dyn RuntimeCandidateMetric<S>>,
19}
20
21impl<S> RuntimeCandidateMetricBinding<S> {
22    pub fn new(
23        name: impl Into<Arc<str>>,
24        metric: Arc<dyn RuntimeCandidateMetric<S>>,
25    ) -> Result<Self, String> {
26        let name = name.into();
27        if name.is_empty() {
28            return Err("candidate metric name must not be empty".to_string());
29        }
30        Ok(Self { name, metric })
31    }
32
33    pub fn name(&self) -> &str {
34        self.name.as_ref()
35    }
36
37    pub fn measure(&self, solution: &S, candidate: &CandidateTraceIdentity) -> f64 {
38        self.metric.measure(solution, candidate)
39    }
40}
41
42impl<S> Clone for RuntimeCandidateMetricBinding<S> {
43    fn clone(&self) -> Self {
44        Self {
45            name: Arc::clone(&self.name),
46            metric: Arc::clone(&self.metric),
47        }
48    }
49}
50
51impl<S> fmt::Debug for RuntimeCandidateMetricBinding<S> {
52    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
53        formatter
54            .debug_struct("RuntimeCandidateMetricBinding")
55            .field("name", &self.name)
56            .finish_non_exhaustive()
57    }
58}
59
60pub struct RuntimeCandidateMetricRegistry<S> {
61    metrics: Vec<RuntimeCandidateMetricBinding<S>>,
62}
63
64impl<S> Clone for RuntimeCandidateMetricRegistry<S> {
65    fn clone(&self) -> Self {
66        Self {
67            metrics: self.metrics.clone(),
68        }
69    }
70}
71
72impl<S> fmt::Debug for RuntimeCandidateMetricRegistry<S> {
73    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
74        formatter
75            .debug_list()
76            .entries(self.metrics.iter().map(RuntimeCandidateMetricBinding::name))
77            .finish()
78    }
79}
80
81impl<S> RuntimeCandidateMetricRegistry<S> {
82    pub fn new(metrics: Vec<RuntimeCandidateMetricBinding<S>>) -> Result<Self, String> {
83        for (index, metric) in metrics.iter().enumerate() {
84            if metrics[..index]
85                .iter()
86                .any(|existing| existing.name() == metric.name())
87            {
88                return Err(format!(
89                    "candidate metric `{}` is declared more than once",
90                    metric.name()
91                ));
92            }
93        }
94        Ok(Self { metrics })
95    }
96
97    pub fn get(&self, name: &str) -> Option<&RuntimeCandidateMetricBinding<S>> {
98        self.metrics.iter().find(|metric| metric.name() == name)
99    }
100}
101
102impl<S> Default for RuntimeCandidateMetricRegistry<S> {
103    fn default() -> Self {
104        Self {
105            metrics: Vec::new(),
106        }
107    }
108}