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::{EvaluationError, Problem};
8use crate::types::SolutionAggregate;
9
10/// Format a metric for CLI- and registry-facing dynamic dispatch.
11///
12/// Dynamic formatting uses the problem value's display form directly.
13pub fn format_metric<T>(metric: &T) -> String
14where
15    T: fmt::Display,
16{
17    metric.to_string()
18}
19
20/// Type-erased problem interface for dynamic dispatch.
21///
22/// Implemented for serializable problems whose values support solution witnesses.
23pub trait DynProblem: Any {
24    /// Whether a completed aggregate admits a representative witness.
25    fn aggregate_witness_evaluation(
26        &self,
27        value: &Value,
28    ) -> Result<Option<String>, EvaluationError>;
29    /// Evaluate a configuration and return the CLI-facing metric string.
30    fn evaluate_dyn(&self, solution: &Value) -> Result<String, EvaluationError>;
31    /// Evaluate a candidate witness, returning `None` when it is infeasible.
32    /// This validates feasibility, not global optimality.
33    fn evaluate_witness_dyn(&self, solution: &Value) -> Result<Option<String>, EvaluationError>;
34    /// Evaluate a configuration and return the result as a serializable JSON value.
35    fn evaluate_json(&self, solution: &Value) -> Result<Value, EvaluationError>;
36    /// Serialize the problem to a JSON value.
37    fn serialize_json(&self) -> Value;
38    /// Downcast to `&dyn Any` for type recovery.
39    fn as_any(&self) -> &dyn Any;
40    /// Return the problem name (`Problem::NAME`).
41    fn problem_name(&self) -> &'static str;
42    /// Return the variant key-value map.
43    fn variant_map(&self) -> BTreeMap<String, String>;
44    /// Return this problem model's canonical parameter names.
45    fn parameter_names_dyn(&self) -> &'static [&'static str];
46    /// Measure the complete canonical parameters of this concrete instance.
47    fn parameters_dyn(&self) -> crate::types::ProblemParameters;
48}
49
50impl<T> DynProblem for T
51where
52    T: Problem + Serialize + 'static,
53    T::Solution: serde::de::DeserializeOwned,
54    T::Value: SolutionAggregate + fmt::Display + Serialize,
55{
56    fn aggregate_witness_evaluation(
57        &self,
58        value: &Value,
59    ) -> Result<Option<String>, EvaluationError> {
60        let value: T::Value = serde::Deserialize::deserialize(value).map_err(|error| {
61            EvaluationError::InvalidConfiguration(format!("invalid aggregate JSON: {error}"))
62        })?;
63        Ok(T::Value::contributes_to_solution(&value, &value).then(|| format_metric(&value)))
64    }
65    fn evaluate_dyn(&self, solution: &Value) -> Result<String, EvaluationError> {
66        let solution = serde::Deserialize::deserialize(solution).map_err(|error| {
67            EvaluationError::InvalidConfiguration(format!("invalid solution JSON: {error}"))
68        })?;
69        Ok(format_metric(&self.evaluate(&solution)?))
70    }
71
72    fn evaluate_json(&self, solution: &Value) -> Result<Value, EvaluationError> {
73        let solution = serde::Deserialize::deserialize(solution).map_err(|error| {
74            EvaluationError::InvalidConfiguration(format!("invalid solution JSON: {error}"))
75        })?;
76        serde_json::to_value(self.evaluate(&solution)?).map_err(|error| {
77            EvaluationError::InvalidConfiguration(format!("cannot serialize evaluation: {error}"))
78        })
79    }
80
81    fn evaluate_witness_dyn(&self, solution: &Value) -> Result<Option<String>, EvaluationError> {
82        let solution = serde::Deserialize::deserialize(solution).map_err(|error| {
83            EvaluationError::InvalidConfiguration(format!("invalid solution JSON: {error}"))
84        })?;
85        let value = self.evaluate(&solution)?;
86        Ok(T::Value::contributes_to_solution(&value, &value).then(|| format_metric(&value)))
87    }
88
89    fn serialize_json(&self) -> Value {
90        serde_json::to_value(self).expect("serialize failed")
91    }
92
93    fn as_any(&self) -> &dyn Any {
94        self
95    }
96
97    fn problem_name(&self) -> &'static str {
98        T::NAME
99    }
100
101    fn variant_map(&self) -> BTreeMap<String, String> {
102        crate::export::variant_to_map(T::variant())
103    }
104
105    fn parameter_names_dyn(&self) -> &'static [&'static str] {
106        T::parameter_names()
107    }
108
109    fn parameters_dyn(&self) -> crate::types::ProblemParameters {
110        self.parameters()
111    }
112}
113
114/// A loaded type-erased problem.
115pub struct LoadedDynProblem {
116    inner: Box<dyn DynProblem>,
117}
118
119impl std::fmt::Debug for LoadedDynProblem {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        f.debug_struct("LoadedDynProblem")
122            .field("name", &self.inner.problem_name())
123            .finish()
124    }
125}
126
127impl LoadedDynProblem {
128    /// Create a new loaded dynamic problem.
129    pub(crate) fn new(inner: Box<dyn DynProblem>) -> Self {
130        Self { inner }
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}