Skip to main content

problemreductions/models/set/
rooted_tree_storage_assignment.rs

1//! Rooted Tree Storage Assignment problem implementation.
2
3use crate::registry::{FieldInfo, ProblemSchemaEntry};
4use crate::traits::Problem;
5use serde::{Deserialize, Serialize};
6use std::collections::HashSet;
7
8inventory::submit! {
9    ProblemSchemaEntry {
10        name: "RootedTreeStorageAssignment",
11        display_name: "Rooted Tree Storage Assignment",
12        aliases: &[],
13        dimensions: &[],
14        module_path: module_path!(),
15        description: "Does there exist a rooted tree whose subset path extensions cost at most K?",
16        fields: &[
17            FieldInfo { name: "universe_size", type_name: "usize", description: "Size of the ground set X" },
18            FieldInfo { name: "subsets", type_name: "Vec<Vec<usize>>", description: "Collection of subsets of X" },
19            FieldInfo { name: "bound", type_name: "usize", description: "Upper bound K on the total extension cost" },
20        ],
21    }
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(try_from = "RootedTreeStorageAssignmentDef")]
26pub struct RootedTreeStorageAssignment {
27    universe_size: usize,
28    subsets: Vec<Vec<usize>>,
29    bound: usize,
30}
31
32#[derive(Debug, Deserialize)]
33struct RootedTreeStorageAssignmentDef {
34    universe_size: usize,
35    subsets: Vec<Vec<usize>>,
36    bound: usize,
37}
38
39impl RootedTreeStorageAssignment {
40    pub fn new(universe_size: usize, subsets: Vec<Vec<usize>>, bound: usize) -> Self {
41        Self::try_new(universe_size, subsets, bound).unwrap_or_else(|err| panic!("{err}"))
42    }
43
44    pub fn try_new(
45        universe_size: usize,
46        subsets: Vec<Vec<usize>>,
47        bound: usize,
48    ) -> Result<Self, String> {
49        let subsets = subsets
50            .into_iter()
51            .enumerate()
52            .map(|(subset_index, mut subset)| {
53                let mut seen = HashSet::with_capacity(subset.len());
54                for &element in &subset {
55                    if element >= universe_size {
56                        return Err(format!(
57                            "subset {subset_index} contains element {element} outside universe of size {universe_size}"
58                        ));
59                    }
60                    if !seen.insert(element) {
61                        return Err(format!(
62                            "subset {subset_index} contains duplicate element {element}"
63                        ));
64                    }
65                }
66                subset.sort_unstable();
67                Ok(subset)
68            })
69            .collect::<Result<Vec<_>, _>>()?;
70
71        Ok(Self {
72            universe_size,
73            subsets,
74            bound,
75        })
76    }
77
78    pub fn universe_size(&self) -> usize {
79        self.universe_size
80    }
81
82    pub fn num_subsets(&self) -> usize {
83        self.subsets.len()
84    }
85
86    pub fn subsets(&self) -> &[Vec<usize>] {
87        &self.subsets
88    }
89
90    pub fn bound(&self) -> usize {
91        self.bound
92    }
93
94    fn analyze_tree(config: &[usize]) -> Option<Vec<usize>> {
95        let roots = config
96            .iter()
97            .enumerate()
98            .filter(|(vertex, parent)| *vertex == **parent)
99            .count();
100        if roots != 1 {
101            return None;
102        }
103
104        let n = config.len();
105        let mut state = vec![0u8; n];
106        let mut depth = vec![0usize; n];
107
108        fn visit(vertex: usize, config: &[usize], state: &mut [u8], depth: &mut [usize]) -> bool {
109            match state[vertex] {
110                2 => return true,
111                1 => return false,
112                _ => {}
113            }
114
115            state[vertex] = 1;
116            let parent = config[vertex];
117            if parent == vertex {
118                depth[vertex] = 0;
119            } else {
120                if !visit(parent, config, state, depth) {
121                    return false;
122                }
123                depth[vertex] = depth[parent] + 1;
124            }
125            state[vertex] = 2;
126            true
127        }
128
129        for vertex in 0..n {
130            if !visit(vertex, config, &mut state, &mut depth) {
131                return None;
132            }
133        }
134
135        Some(depth)
136    }
137
138    fn is_ancestor(ancestor: usize, mut vertex: usize, config: &[usize], depth: &[usize]) -> bool {
139        if depth[ancestor] > depth[vertex] {
140            return false;
141        }
142
143        while depth[vertex] > depth[ancestor] {
144            vertex = config[vertex];
145        }
146
147        ancestor == vertex
148    }
149
150    fn subset_extension_cost(
151        &self,
152        subset: &[usize],
153        config: &[usize],
154        depth: &[usize],
155    ) -> Option<usize> {
156        if subset.len() <= 1 {
157            return Some(0);
158        }
159
160        let mut ordered = subset.to_vec();
161        ordered.sort_by_key(|&vertex| depth[vertex]);
162
163        for pair in ordered.windows(2) {
164            if !Self::is_ancestor(pair[0], pair[1], config, depth) {
165                return None;
166            }
167        }
168
169        let top = ordered[0];
170        let bottom = *ordered.last().unwrap();
171        Some(depth[bottom] - depth[top] + 1 - ordered.len())
172    }
173}
174
175impl Problem for RootedTreeStorageAssignment {
176    const NAME: &'static str = "RootedTreeStorageAssignment";
177    type Value = crate::types::Or;
178
179    fn dims(&self) -> Vec<usize> {
180        vec![self.universe_size; self.universe_size]
181    }
182
183    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
184        crate::types::Or({
185            if config.len() != self.universe_size {
186                return crate::types::Or(false);
187            }
188            if config.iter().any(|&parent| parent >= self.universe_size) {
189                return crate::types::Or(false);
190            }
191            if self.universe_size == 0 {
192                return crate::types::Or(self.subsets.is_empty());
193            }
194
195            let Some(depth) = Self::analyze_tree(config) else {
196                return crate::types::Or(false);
197            };
198
199            let mut total_cost = 0usize;
200            for subset in &self.subsets {
201                let Some(cost) = self.subset_extension_cost(subset, config, &depth) else {
202                    return crate::types::Or(false);
203                };
204                total_cost += cost;
205                if total_cost > self.bound {
206                    return crate::types::Or(false);
207                }
208            }
209
210            true
211        })
212    }
213
214    fn variant() -> Vec<(&'static str, &'static str)> {
215        crate::variant_params![]
216    }
217}
218
219crate::declare_variants! {
220    default RootedTreeStorageAssignment => "universe_size^universe_size",
221}
222
223#[cfg(feature = "example-db")]
224pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
225    vec![crate::example_db::specs::ModelExampleSpec {
226        id: "rooted_tree_storage_assignment",
227        instance: Box::new(RootedTreeStorageAssignment::new(
228            5,
229            vec![vec![0, 2], vec![1, 3], vec![0, 4], vec![2, 4]],
230            1,
231        )),
232        optimal_config: vec![0, 0, 0, 1, 2],
233        optimal_value: serde_json::json!(true),
234    }]
235}
236
237impl TryFrom<RootedTreeStorageAssignmentDef> for RootedTreeStorageAssignment {
238    type Error = String;
239
240    fn try_from(value: RootedTreeStorageAssignmentDef) -> Result<Self, Self::Error> {
241        Self::try_new(value.universe_size, value.subsets, value.bound)
242    }
243}
244
245#[cfg(test)]
246#[path = "../../unit_tests/models/set/rooted_tree_storage_assignment.rs"]
247mod tests;