Skip to main content

problemreductions/
variant.rs

1//! Variant system for type-level problem parameterization.
2//!
3//! Types declare their variant category, value, and parent via `VariantParam`.
4//! The `impl_variant_param!` macro registers types with the trait.
5//! The `variant_params!` macro composes `Problem::variant()` bodies from type parameter names.
6
7/// A type that participates in the variant system.
8///
9/// Declares its category (e.g., `"graph"`), value (e.g., `"SimpleGraph"`),
10/// and optional parent in the subtype hierarchy.
11pub trait VariantParam: 'static {
12    /// Category name (e.g., `"graph"`, `"weight"`, `"k"`).
13    const CATEGORY: &'static str;
14    /// Type name within the category (e.g., `"SimpleGraph"`, `"i32"`).
15    const VALUE: &'static str;
16    /// Parent type name in the subtype hierarchy, or `None` for root types.
17    const PARENT_VALUE: Option<&'static str>;
18}
19
20/// Types that can convert themselves to their parent in the variant hierarchy.
21pub trait CastToParent: VariantParam {
22    /// The parent type.
23    type Parent: VariantParam;
24    /// Convert this value to its parent type.
25    fn cast_to_parent(&self) -> Self::Parent;
26}
27
28/// K-value marker trait for types that represent a const-generic K parameter.
29///
30/// Types implementing this trait declare an optional K value. `None` means
31/// the type represents an arbitrary K (like KN), while `Some(k)` means
32/// a specific value (like K2, K3).
33pub trait KValue: VariantParam + Clone + 'static {
34    /// The K value, or `None` for arbitrary K.
35    const K: Option<usize>;
36}
37
38/// Implement `VariantParam` (and optionally `CastToParent` and/or `KValue`) for a type.
39///
40/// # Usage
41///
42/// ```text
43/// // Root type (no parent):
44/// impl_variant_param!(SimpleGraph, "graph");
45///
46/// // Type with parent -- cast closure required:
47/// impl_variant_param!(UnitDiskGraph, "graph", parent: SimpleGraph,
48///     cast: |g| SimpleGraph::new(g.num_vertices(), g.edges()));
49///
50/// // Root K type (no parent, with K value):
51/// impl_variant_param!(KN, "k", k: None);
52///
53/// // K type with parent + cast + K value:
54/// impl_variant_param!(K3, "k", parent: KN, cast: |_| KN, k: Some(3));
55/// ```
56#[macro_export]
57macro_rules! impl_variant_param {
58    // Root type (no parent, no cast)
59    ($ty:ty, $cat:expr) => {
60        impl $crate::variant::VariantParam for $ty {
61            const CATEGORY: &'static str = $cat;
62            const VALUE: &'static str = stringify!($ty);
63            const PARENT_VALUE: Option<&'static str> = None;
64        }
65    };
66    // Type with parent + cast closure
67    ($ty:ty, $cat:expr, parent: $parent:ty, cast: $cast:expr) => {
68        impl $crate::variant::VariantParam for $ty {
69            const CATEGORY: &'static str = $cat;
70            const VALUE: &'static str = stringify!($ty);
71            const PARENT_VALUE: Option<&'static str> = Some(stringify!($parent));
72        }
73        impl $crate::variant::CastToParent for $ty {
74            type Parent = $parent;
75            fn cast_to_parent(&self) -> $parent {
76                let f: fn(&$ty) -> $parent = $cast;
77                f(self)
78            }
79        }
80    };
81    // KValue root type (no parent, with k value)
82    ($ty:ty, $cat:expr, k: $k:expr) => {
83        $crate::impl_variant_param!($ty, $cat);
84        impl $crate::variant::KValue for $ty {
85            const K: Option<usize> = $k;
86        }
87    };
88    // KValue type with parent + cast + k value
89    ($ty:ty, $cat:expr, parent: $parent:ty, cast: $cast:expr, k: $k:expr) => {
90        $crate::impl_variant_param!($ty, $cat, parent: $parent, cast: $cast);
91        impl $crate::variant::KValue for $ty {
92            const K: Option<usize> = $k;
93        }
94    };
95}
96
97/// Compose a `Problem::variant()` body from type parameter names.
98///
99/// All variant dimensions must be types implementing `VariantParam`.
100///
101/// # Usage
102///
103/// ```text
104/// variant_params![]           // -> vec![]
105/// variant_params![G, W]       // -> vec![(G::CATEGORY, G::VALUE), ...]
106/// ```
107#[macro_export]
108macro_rules! variant_params {
109    () => { vec![] };
110    ($($T:ident),+) => {
111        vec![$((<$T as $crate::variant::VariantParam>::CATEGORY,
112              <$T as $crate::variant::VariantParam>::VALUE)),+]
113    };
114}
115
116// --- Concrete KValue types ---
117
118/// K=1 (e.g., 1-coloring).
119#[derive(Clone, Copy, Debug, Default)]
120pub struct K1;
121
122/// K=2 (e.g., 2-SAT, 2-coloring).
123#[derive(Clone, Copy, Debug, Default)]
124pub struct K2;
125
126/// K=3 (e.g., 3-SAT, 3-coloring).
127#[derive(Clone, Copy, Debug, Default)]
128pub struct K3;
129
130/// K=4 (e.g., 4-coloring).
131#[derive(Clone, Copy, Debug, Default)]
132pub struct K4;
133
134/// K=5 (e.g., 5-coloring).
135#[derive(Clone, Copy, Debug, Default)]
136pub struct K5;
137
138/// Generic K (any value). Used for reductions that apply to all K.
139#[derive(Clone, Copy, Debug, Default)]
140pub struct KN;
141
142impl_variant_param!(KN, "k", k: None);
143impl_variant_param!(K5, "k", parent: KN, cast: |_| KN, k: Some(5));
144impl_variant_param!(K4, "k", parent: KN, cast: |_| KN, k: Some(4));
145impl_variant_param!(K3, "k", parent: KN, cast: |_| KN, k: Some(3));
146impl_variant_param!(K2, "k", parent: KN, cast: |_| KN, k: Some(2));
147impl_variant_param!(K1, "k", parent: KN, cast: |_| KN, k: Some(1));
148
149// --- VariantSpec: canonical runtime representation of a problem variant ---
150
151use std::collections::BTreeMap;
152
153/// Canonical runtime representation of a problem variant.
154///
155/// Used for validated runtime lookups and normalization. Unlike raw
156/// `BTreeMap<String, String>`, a `VariantSpec` validates its dimensions
157/// at construction time and can normalize default values.
158#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
159pub struct VariantSpec {
160    dims: BTreeMap<String, String>,
161}
162
163/// Default dimension values used for normalization and default detection.
164const DEFAULT_VALUES: &[&str] = &["SimpleGraph", "One", "KN"];
165
166impl VariantSpec {
167    /// Create a `VariantSpec` from key-value pairs, rejecting duplicate dimensions.
168    ///
169    /// Returns an error if the same dimension key appears more than once.
170    pub fn try_from_pairs<I, K, V>(pairs: I) -> std::result::Result<Self, String>
171    where
172        I: IntoIterator<Item = (K, V)>,
173        K: Into<String>,
174        V: Into<String>,
175    {
176        let mut dims = BTreeMap::new();
177        for (k, v) in pairs {
178            let key = k.into();
179            let val = v.into();
180            if dims.insert(key.clone(), val).is_some() {
181                return Err(format!("duplicate dimension: {}", key));
182            }
183        }
184        Ok(Self { dims })
185    }
186
187    /// Create a `VariantSpec` from an existing `BTreeMap`.
188    pub fn try_from_map(map: BTreeMap<String, String>) -> std::result::Result<Self, String> {
189        Ok(Self { dims: map })
190    }
191
192    /// View the dimensions as a map.
193    pub fn as_map(&self) -> &BTreeMap<String, String> {
194        &self.dims
195    }
196
197    /// Consume this `VariantSpec` and return the underlying map.
198    pub fn into_map(self) -> BTreeMap<String, String> {
199        self.dims
200    }
201
202    /// Update or add a single dimension.
203    pub fn update_dimension(&mut self, key: impl Into<String>, value: impl Into<String>) {
204        self.dims.insert(key.into(), value.into());
205    }
206
207    /// Normalize the variant by filling in default values for empty dimensions.
208    ///
209    /// If a dimension has an empty string value, it is replaced with its
210    /// canonical default:
211    /// - `"graph"` → `"SimpleGraph"`
212    pub fn normalize(&self) -> Self {
213        let mut dims = self.dims.clone();
214        if let Some(v) = dims.get_mut("graph") {
215            if v.is_empty() {
216                *v = "SimpleGraph".to_string();
217            }
218        }
219        Self { dims }
220    }
221
222    /// Check whether this variant uses only default dimension values.
223    ///
224    /// Returns `true` if every dimension value is one of the recognized
225    /// defaults: `"SimpleGraph"`, `"One"`, `"KN"`. An empty variant
226    /// (no dimensions) is also considered default.
227    pub fn is_default(&self) -> bool {
228        self.dims
229            .values()
230            .all(|v| DEFAULT_VALUES.contains(&v.as_str()))
231    }
232}
233
234#[cfg(test)]
235#[path = "unit_tests/variant.rs"]
236mod tests;