Skip to main content

problemreductions/models/
decision.rs

1//! Generic decision wrapper for optimization problems.
2
3use crate::rules::{AggregateReductionResult, ReduceTo, ReduceToAggregate, ReductionResult};
4use crate::traits::Problem;
5use crate::types::{OptimizationValue, Or};
6use serde::de::DeserializeOwned;
7use serde::{Deserialize, Serialize};
8
9/// Metadata for concrete optimization problems that expose a decision wrapper.
10pub trait DecisionProblemMeta: Problem
11where
12    Self::Value: OptimizationValue,
13{
14    /// Problem name used by the corresponding `Decision<Self>` variant.
15    const DECISION_NAME: &'static str;
16}
17
18/// Register the decision problem name for a concrete optimization problem.
19#[macro_export]
20macro_rules! decision_problem_meta {
21    ($inner:ty, $name:literal) => {
22        impl $crate::models::decision::DecisionProblemMeta for $inner {
23            const DECISION_NAME: &'static str = $name;
24        }
25    };
26}
27
28/// Register the boilerplate inventory entries for a concrete `Decision<P>` variant.
29///
30/// The `size_getters` parameter defines problem-specific size fields as
31/// `(name, getter_on_inner)` pairs, e.g., `[("num_vertices", num_vertices), ("num_edges", num_edges)]`.
32/// These are used for overhead expressions and `ProblemSize` extraction.
33/// The macro automatically adds a `("k", k)` entry for `source_size_fn` on the Decision side.
34///
35/// Callers must define inherent methods on `Decision<Inner>` (delegating to `self.inner()`)
36/// and a `k()` method (from `self.bound()`) **before** invoking this macro.
37#[macro_export]
38macro_rules! register_decision_variant {
39    (
40        $inner:ty,
41        $name:literal,
42        $complexity:literal,
43        $aliases:expr,
44        $description:literal,
45        dims: [$($dim:expr),* $(,)?],
46        fields: [$($field:expr),* $(,)?],
47        size_getters: [$(($sg_name:literal, $sg_method:ident)),* $(,)?]
48    ) => {
49        $crate::declare_variants! {
50            default $crate::models::decision::Decision<$inner> => $complexity,
51        }
52
53        $crate::inventory::submit! {
54            $crate::registry::ProblemSchemaEntry {
55                name: $name,
56                display_name: $crate::register_decision_variant!(@display_name $name),
57                aliases: $aliases,
58                dimensions: &[$($dim),*],
59                module_path: module_path!(),
60                description: $description,
61                fields: &[$($field),*],
62            }
63        }
64
65        // Decision<P> → P: both witness (identity config) and aggregate (solve + compare)
66        $crate::inventory::submit! {
67            $crate::rules::ReductionEntry {
68                source_name: $name,
69                target_name: <$inner as $crate::traits::Problem>::NAME,
70                source_variant_fn: <$crate::models::decision::Decision<$inner> as $crate::traits::Problem>::variant,
71                target_variant_fn: <$inner as $crate::traits::Problem>::variant,
72                overhead_fn: || $crate::rules::ReductionOverhead::identity(&[$($sg_name),*]),
73                module_path: module_path!(),
74                reduce_fn: Some(|any| {
75                    let source = any
76                        .downcast_ref::<$crate::models::decision::Decision<$inner>>()
77                        .expect(concat!($name, " witness reduction source type mismatch"));
78                    Box::new(
79                        <$crate::models::decision::Decision<$inner> as $crate::rules::ReduceTo<$inner>>::reduce_to(source),
80                    )
81                }),
82                reduce_aggregate_fn: Some(|any| {
83                    let source = any
84                        .downcast_ref::<$crate::models::decision::Decision<$inner>>()
85                        .expect(concat!($name, " aggregate reduction source type mismatch"));
86                    Box::new(
87                        <$crate::models::decision::Decision<$inner> as $crate::rules::ReduceToAggregate<$inner>>::reduce_to_aggregate(source),
88                    )
89                }),
90                capabilities: $crate::rules::EdgeCapabilities::both(),
91                overhead_eval_fn: |any| {
92                    let source = any
93                        .downcast_ref::<$crate::models::decision::Decision<$inner>>()
94                        .expect(concat!($name, " overhead source type mismatch"));
95                    $crate::types::ProblemSize::new(vec![
96                        $(($sg_name, source.$sg_method())),*
97                    ])
98                },
99                source_size_fn: |any| {
100                    let source = any
101                        .downcast_ref::<$crate::models::decision::Decision<$inner>>()
102                        .expect(concat!($name, " size source type mismatch"));
103                    $crate::types::ProblemSize::new(vec![
104                        $(($sg_name, source.$sg_method()),)*
105                        ("k", source.k()),
106                    ])
107                },
108            }
109        }
110
111        // Reverse edge: P → Decision<P> (Turing/multi-query reduction via binary search)
112        $crate::inventory::submit! {
113            $crate::rules::ReductionEntry {
114                source_name: <$inner as $crate::traits::Problem>::NAME,
115                target_name: $name,
116                source_variant_fn: <$inner as $crate::traits::Problem>::variant,
117                target_variant_fn: <$crate::models::decision::Decision<$inner> as $crate::traits::Problem>::variant,
118                overhead_fn: || $crate::rules::ReductionOverhead::identity(&[$($sg_name),*]),
119                module_path: module_path!(),
120                reduce_fn: None,
121                reduce_aggregate_fn: None,
122                capabilities: $crate::rules::EdgeCapabilities::turing(),
123                overhead_eval_fn: |any| {
124                    let source = any
125                        .downcast_ref::<$inner>()
126                        .expect(concat!($name, " turing overhead source type mismatch"));
127                    $crate::types::ProblemSize::new(vec![
128                        $(($sg_name, source.$sg_method())),*
129                    ])
130                },
131                source_size_fn: |any| {
132                    let source = any
133                        .downcast_ref::<$inner>()
134                        .expect(concat!($name, " turing size source type mismatch"));
135                    $crate::types::ProblemSize::new(vec![
136                        $(($sg_name, source.$sg_method())),*
137                    ])
138                },
139            }
140        }
141    };
142    (@display_name "DecisionMinimumVertexCover") => {
143        "Decision Minimum Vertex Cover"
144    };
145    (@display_name "DecisionMinimumDominatingSet") => {
146        "Decision Minimum Dominating Set"
147    };
148    (@display_name "DecisionMaximumIndependentSet") => {
149        "Decision Maximum Independent Set"
150    };
151    (@display_name $name:literal) => {
152        $name
153    };
154}
155
156/// Decision version of an optimization problem with a fixed objective bound.
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct Decision<P: Problem>
159where
160    P::Value: OptimizationValue,
161{
162    inner: P,
163    bound: <P::Value as OptimizationValue>::Inner,
164}
165
166impl<P: Problem> Decision<P>
167where
168    P::Value: OptimizationValue,
169{
170    /// Create a decision wrapper around `inner` with the provided bound.
171    pub fn new(inner: P, bound: <P::Value as OptimizationValue>::Inner) -> Self {
172        Self { inner, bound }
173    }
174
175    /// Borrow the wrapped optimization problem.
176    pub fn inner(&self) -> &P {
177        &self.inner
178    }
179
180    /// Borrow the decision bound.
181    pub fn bound(&self) -> &<P::Value as OptimizationValue>::Inner {
182        &self.bound
183    }
184}
185
186impl<P> Problem for Decision<P>
187where
188    P: DecisionProblemMeta,
189    P::Value: OptimizationValue,
190{
191    const NAME: &'static str = P::DECISION_NAME;
192    type Value = Or;
193
194    fn dims(&self) -> Vec<usize> {
195        self.inner.dims()
196    }
197
198    fn evaluate(&self, config: &[usize]) -> Or {
199        Or(<P::Value as OptimizationValue>::meets_bound(
200            &self.inner.evaluate(config),
201            &self.bound,
202        ))
203    }
204
205    fn variant() -> Vec<(&'static str, &'static str)> {
206        P::variant()
207    }
208}
209
210/// Aggregate reduction result for `Decision<P> -> P`.
211#[derive(Debug, Clone)]
212pub struct DecisionToOptimizationResult<P>
213where
214    P: Problem,
215    P::Value: OptimizationValue,
216{
217    target: P,
218    bound: <P::Value as OptimizationValue>::Inner,
219}
220
221impl<P> AggregateReductionResult for DecisionToOptimizationResult<P>
222where
223    P: DecisionProblemMeta + 'static,
224    P::Value: OptimizationValue + Serialize + DeserializeOwned,
225{
226    type Source = Decision<P>;
227    type Target = P;
228
229    fn target_problem(&self) -> &Self::Target {
230        &self.target
231    }
232
233    fn extract_value(&self, target_value: P::Value) -> Or {
234        Or(<P::Value as OptimizationValue>::meets_bound(
235            &target_value,
236            &self.bound,
237        ))
238    }
239}
240
241impl<P> ReduceToAggregate<P> for Decision<P>
242where
243    P: DecisionProblemMeta + Clone + 'static,
244    P::Value: OptimizationValue + Serialize + DeserializeOwned,
245{
246    type Result = DecisionToOptimizationResult<P>;
247
248    fn reduce_to_aggregate(&self) -> Self::Result {
249        DecisionToOptimizationResult {
250            target: self.inner.clone(),
251            bound: self.bound.clone(),
252        }
253    }
254}
255
256/// Witness reduction result for `Decision<P> -> P`.
257///
258/// The configuration spaces are identical — a config that is optimal for
259/// `P` and meets the bound is a valid `Decision<P>` witness. The
260/// `extract_solution` is the identity function.
261#[derive(Debug, Clone)]
262pub struct DecisionToOptimizationWitnessResult<P>
263where
264    P: Problem,
265    P::Value: OptimizationValue,
266{
267    target: P,
268}
269
270impl<P> ReductionResult for DecisionToOptimizationWitnessResult<P>
271where
272    P: DecisionProblemMeta + 'static,
273    P::Value: OptimizationValue + Serialize + DeserializeOwned,
274{
275    type Source = Decision<P>;
276    type Target = P;
277
278    fn target_problem(&self) -> &Self::Target {
279        &self.target
280    }
281
282    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
283        target_solution.to_vec()
284    }
285}
286
287impl<P> ReduceTo<P> for Decision<P>
288where
289    P: DecisionProblemMeta + Clone + 'static,
290    P::Value: OptimizationValue + Serialize + DeserializeOwned,
291{
292    type Result = DecisionToOptimizationWitnessResult<P>;
293
294    fn reduce_to(&self) -> Self::Result {
295        DecisionToOptimizationWitnessResult {
296            target: self.inner.clone(),
297        }
298    }
299}
300
301#[cfg(test)]
302#[path = "../unit_tests/models/decision.rs"]
303mod tests;