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    /// Evaluate a configuration and return the CLI-facing metric string.
25    fn evaluate_dyn(&self, solution: &Value) -> Result<String, EvaluationError>;
26    /// Evaluate a candidate witness, returning `None` when it is infeasible.
27    /// This validates feasibility, not global optimality.
28    fn evaluate_witness_dyn(&self, solution: &Value) -> Result<Option<String>, EvaluationError>;
29    /// Evaluate a configuration and return the result as a serializable JSON value.
30    fn evaluate_json(&self, solution: &Value) -> Result<Value, EvaluationError>;
31    /// Serialize the problem to a JSON value.
32    fn serialize_json(&self) -> Value;
33    /// Downcast to `&dyn Any` for type recovery.
34    fn as_any(&self) -> &dyn Any;
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 this problem model's canonical parameter names.
40    fn parameter_names_dyn(&self) -> &'static [&'static str];
41    /// Measure the complete canonical parameters of this concrete instance.
42    fn parameters_dyn(&self) -> crate::types::ProblemParameters;
43}
44
45impl<T> DynProblem for T
46where
47    T: Problem + Serialize + 'static,
48    T::Solution: serde::de::DeserializeOwned,
49    T::Value: SolutionAggregate + fmt::Display + Serialize,
50{
51    fn evaluate_dyn(&self, solution: &Value) -> Result<String, EvaluationError> {
52        let solution = serde::Deserialize::deserialize(solution).map_err(|error| {
53            EvaluationError::InvalidConfiguration(format!("invalid solution JSON: {error}"))
54        })?;
55        Ok(format_metric(&self.evaluate(&solution)?))
56    }
57
58    fn evaluate_json(&self, solution: &Value) -> Result<Value, EvaluationError> {
59        let solution = serde::Deserialize::deserialize(solution).map_err(|error| {
60            EvaluationError::InvalidConfiguration(format!("invalid solution JSON: {error}"))
61        })?;
62        Ok(serde_json::to_value(self.evaluate(&solution)?).expect("serialize metric failed"))
63    }
64
65    fn evaluate_witness_dyn(&self, solution: &Value) -> Result<Option<String>, EvaluationError> {
66        let solution = serde::Deserialize::deserialize(solution).map_err(|error| {
67            EvaluationError::InvalidConfiguration(format!("invalid solution JSON: {error}"))
68        })?;
69        let value = self.evaluate(&solution)?;
70        Ok(T::Value::contributes_to_solution(&value, &value).then(|| format_metric(&value)))
71    }
72
73    fn serialize_json(&self) -> Value {
74        serde_json::to_value(self).expect("serialize failed")
75    }
76
77    fn as_any(&self) -> &dyn Any {
78        self
79    }
80
81    fn problem_name(&self) -> &'static str {
82        T::NAME
83    }
84
85    fn variant_map(&self) -> BTreeMap<String, String> {
86        crate::export::variant_to_map(T::variant())
87    }
88
89    fn parameter_names_dyn(&self) -> &'static [&'static str] {
90        T::parameter_names()
91    }
92
93    fn parameters_dyn(&self) -> crate::types::ProblemParameters {
94        self.parameters()
95    }
96}
97
98/// A loaded type-erased problem.
99pub struct LoadedDynProblem {
100    inner: Box<dyn DynProblem>,
101}
102
103impl std::fmt::Debug for LoadedDynProblem {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        f.debug_struct("LoadedDynProblem")
106            .field("name", &self.inner.problem_name())
107            .finish()
108    }
109}
110
111impl LoadedDynProblem {
112    /// Create a new loaded dynamic problem.
113    pub(crate) fn new(inner: Box<dyn DynProblem>) -> Self {
114        Self { inner }
115    }
116}
117
118impl std::ops::Deref for LoadedDynProblem {
119    type Target = dyn DynProblem;
120
121    fn deref(&self) -> &(dyn DynProblem + 'static) {
122        &*self.inner
123    }
124}