Skip to main content

problemreductions/registry/
dyn_problem.rs

1use serde::Serialize;
2use serde_json::Value;
3use std::any::Any;
4use std::collections::BTreeMap;
5use std::fmt;
6
7use crate::traits::Problem;
8
9/// Format a metric for CLI- and registry-facing dynamic dispatch.
10///
11/// Dynamic formatting uses the aggregate display form directly, so optimization
12/// metrics appear as `Max(...)` / `Min(...)` alongside aggregate-only values
13/// such as `Or(true)` or `Sum(56)`.
14pub fn format_metric<T>(metric: &T) -> String
15where
16    T: fmt::Display,
17{
18    metric.to_string()
19}
20
21/// Type-erased problem interface for dynamic dispatch.
22///
23/// Implemented via blanket impl for any `T: Problem + Serialize + 'static`.
24pub trait DynProblem: Any {
25    /// Evaluate a configuration and return the CLI-facing metric string.
26    fn evaluate_dyn(&self, config: &[usize]) -> String;
27    /// Evaluate a configuration and return the result as a serializable JSON value.
28    fn evaluate_json(&self, config: &[usize]) -> Value;
29    /// Serialize the problem to a JSON value.
30    fn serialize_json(&self) -> Value;
31    /// Downcast to `&dyn Any` for type recovery.
32    fn as_any(&self) -> &dyn Any;
33    /// Return the configuration space dimensions.
34    fn dims_dyn(&self) -> Vec<usize>;
35    /// Return the problem name (`Problem::NAME`).
36    fn problem_name(&self) -> &'static str;
37    /// Return the variant key-value map.
38    fn variant_map(&self) -> BTreeMap<String, String>;
39    /// Return the number of variables.
40    fn num_variables_dyn(&self) -> usize;
41}
42
43impl<T> DynProblem for T
44where
45    T: Problem + Serialize + 'static,
46    T::Value: fmt::Display + Serialize,
47{
48    fn evaluate_dyn(&self, config: &[usize]) -> String {
49        format_metric(&self.evaluate(config))
50    }
51
52    fn evaluate_json(&self, config: &[usize]) -> Value {
53        serde_json::to_value(self.evaluate(config)).expect("serialize metric failed")
54    }
55
56    fn serialize_json(&self) -> Value {
57        serde_json::to_value(self).expect("serialize failed")
58    }
59
60    fn as_any(&self) -> &dyn Any {
61        self
62    }
63
64    fn dims_dyn(&self) -> Vec<usize> {
65        self.dims()
66    }
67
68    fn problem_name(&self) -> &'static str {
69        T::NAME
70    }
71
72    fn variant_map(&self) -> BTreeMap<String, String> {
73        crate::export::variant_to_map(T::variant())
74    }
75
76    fn num_variables_dyn(&self) -> usize {
77        self.num_variables()
78    }
79}
80
81/// Function pointer type for brute-force value solve dispatch.
82pub type SolveValueFn = fn(&dyn Any) -> String;
83
84/// Function pointer type for brute-force witness solve dispatch.
85pub type SolveWitnessFn = fn(&dyn Any) -> Option<(Vec<usize>, String)>;
86
87/// A loaded problem with type-erased solve capability.
88///
89/// Wraps a `Box<dyn DynProblem>` with brute-force value and witness function pointers.
90pub struct LoadedDynProblem {
91    inner: Box<dyn DynProblem>,
92    solve_value_fn: SolveValueFn,
93    solve_witness_fn: SolveWitnessFn,
94}
95
96impl std::fmt::Debug for LoadedDynProblem {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        f.debug_struct("LoadedDynProblem")
99            .field("name", &self.inner.problem_name())
100            .finish()
101    }
102}
103
104impl LoadedDynProblem {
105    /// Create a new loaded dynamic problem.
106    pub fn new(
107        inner: Box<dyn DynProblem>,
108        solve_value_fn: SolveValueFn,
109        solve_witness_fn: SolveWitnessFn,
110    ) -> Self {
111        Self {
112            inner,
113            solve_value_fn,
114            solve_witness_fn,
115        }
116    }
117
118    /// Solve the problem using brute force and return its aggregate value string.
119    pub fn solve_brute_force_value(&self) -> String {
120        (self.solve_value_fn)(self.inner.as_any())
121    }
122
123    /// Solve the problem using brute force and return a witness when available.
124    pub fn solve_brute_force_witness(&self) -> Option<(Vec<usize>, String)> {
125        (self.solve_witness_fn)(self.inner.as_any())
126    }
127
128    /// Backward-compatible witness solve entry point.
129    pub fn solve_brute_force(&self) -> Option<(Vec<usize>, String)> {
130        self.solve_brute_force_witness()
131    }
132}
133
134impl std::ops::Deref for LoadedDynProblem {
135    type Target = dyn DynProblem;
136
137    fn deref(&self) -> &(dyn DynProblem + 'static) {
138        &*self.inner
139    }
140}