problemreductions/models/misc/
minimum_fault_detection_test_set.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
8use crate::traits::Problem;
9use crate::types::Min;
10use serde::{Deserialize, Deserializer, Serialize};
11use std::collections::{HashSet, VecDeque};
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "MinimumFaultDetectionTestSet",
16 display_name: "Minimum Fault Detection Test Set",
17 aliases: &[],
18 dimensions: &[],
19 category: crate::registry::ProblemCategory::Misc,
20 module_path: module_path!(),
21 description: "Find minimum set of input-output paths covering all internal DAG vertices",
22 fields: &[
23 FieldInfo { name: "num_vertices", type_name: "usize", description: "Number of vertices in the DAG" },
24 FieldInfo { name: "arcs", type_name: "Vec<(usize, usize)>", description: "Directed arcs (u, v)" },
25 FieldInfo { name: "inputs", type_name: "Vec<usize>", description: "Input vertex indices" },
26 FieldInfo { name: "outputs", type_name: "Vec<usize>", description: "Output vertex indices" },
27 ],
28 }
29}
30
31#[derive(Debug, Clone, Serialize)]
61pub struct MinimumFaultDetectionTestSet {
62 num_vertices: usize,
64 arcs: Vec<(usize, usize)>,
66 inputs: Vec<usize>,
68 outputs: Vec<usize>,
70 #[serde(skip)]
73 coverage: Vec<HashSet<usize>>,
74}
75
76#[derive(Deserialize)]
77struct MinimumFaultDetectionTestSetData {
78 num_vertices: usize,
79 arcs: Vec<(usize, usize)>,
80 inputs: Vec<usize>,
81 outputs: Vec<usize>,
82}
83
84impl<'de> Deserialize<'de> for MinimumFaultDetectionTestSet {
85 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
86 where
87 D: Deserializer<'de>,
88 {
89 let data = MinimumFaultDetectionTestSetData::deserialize(deserializer)?;
90 Self::try_new(data.num_vertices, data.arcs, data.inputs, data.outputs)
91 .map_err(serde::de::Error::custom)
92 }
93}
94
95impl MinimumFaultDetectionTestSet {
96 pub fn new(
103 num_vertices: usize,
104 arcs: Vec<(usize, usize)>,
105 inputs: Vec<usize>,
106 outputs: Vec<usize>,
107 ) -> Self {
108 Self::try_new(num_vertices, arcs, inputs, outputs).unwrap_or_else(|error| panic!("{error}"))
109 }
110
111 fn try_new(
112 num_vertices: usize,
113 arcs: Vec<(usize, usize)>,
114 inputs: Vec<usize>,
115 outputs: Vec<usize>,
116 ) -> Result<Self, crate::registry::ConstructionError> {
117 if inputs.is_empty() {
118 return Err("Inputs must not be empty".into());
119 }
120 if outputs.is_empty() {
121 return Err("Outputs must not be empty".into());
122 }
123 for (i, &(u, v)) in arcs.iter().enumerate() {
124 if u >= num_vertices || v >= num_vertices {
125 return Err(format!(
126 "Arc {} ({}, {}) out of bounds for {} vertices",
127 i, u, v, num_vertices
128 )
129 .into());
130 }
131 }
132 for &inp in &inputs {
133 if inp >= num_vertices {
134 return Err(format!(
135 "Input vertex {} out of bounds for {} vertices",
136 inp, num_vertices
137 )
138 .into());
139 }
140 }
141 for &out in &outputs {
142 if out >= num_vertices {
143 return Err(format!(
144 "Output vertex {} out of bounds for {} vertices",
145 out, num_vertices
146 )
147 .into());
148 }
149 }
150 let coverage = Self::build_coverage(num_vertices, &arcs, &inputs, &outputs);
151 Ok(Self {
152 num_vertices,
153 arcs,
154 inputs,
155 outputs,
156 coverage,
157 })
158 }
159
160 fn forward_reachable(num_vertices: usize, adj: &[Vec<usize>], start: usize) -> HashSet<usize> {
162 let mut visited = HashSet::new();
163 let mut queue = VecDeque::new();
164 visited.insert(start);
165 queue.push_back(start);
166 while let Some(v) = queue.pop_front() {
167 if v < adj.len() {
168 for &w in &adj[v] {
169 if visited.insert(w) {
170 queue.push_back(w);
171 }
172 }
173 }
174 }
175 let _ = num_vertices; visited
177 }
178
179 fn backward_reachable(
181 num_vertices: usize,
182 rev_adj: &[Vec<usize>],
183 start: usize,
184 ) -> HashSet<usize> {
185 let mut visited = HashSet::new();
186 let mut queue = VecDeque::new();
187 visited.insert(start);
188 queue.push_back(start);
189 while let Some(v) = queue.pop_front() {
190 if v < rev_adj.len() {
191 for &w in &rev_adj[v] {
192 if visited.insert(w) {
193 queue.push_back(w);
194 }
195 }
196 }
197 }
198 let _ = num_vertices;
199 visited
200 }
201
202 fn build_coverage(
204 num_vertices: usize,
205 arcs: &[(usize, usize)],
206 inputs: &[usize],
207 outputs: &[usize],
208 ) -> Vec<HashSet<usize>> {
209 let mut adj = vec![vec![]; num_vertices];
211 let mut rev_adj = vec![vec![]; num_vertices];
212 for &(u, v) in arcs {
213 adj[u].push(v);
214 rev_adj[v].push(u);
215 }
216
217 let fwd: Vec<HashSet<usize>> = inputs
219 .iter()
220 .map(|&inp| Self::forward_reachable(num_vertices, &adj, inp))
221 .collect();
222
223 let bwd: Vec<HashSet<usize>> = outputs
225 .iter()
226 .map(|&out| Self::backward_reachable(num_vertices, &rev_adj, out))
227 .collect();
228
229 let num_outputs = outputs.len();
230 let mut coverage = Vec::with_capacity(inputs.len() * num_outputs);
231 for (i_idx, _) in inputs.iter().enumerate() {
232 for (o_idx, _) in outputs.iter().enumerate() {
233 let cov: HashSet<usize> = fwd[i_idx].intersection(&bwd[o_idx]).copied().collect();
235 coverage.push(cov);
236 }
237 }
238 coverage
239 }
240
241 pub fn num_vertices(&self) -> usize {
243 self.num_vertices
244 }
245
246 pub fn num_arcs(&self) -> usize {
248 self.arcs.len()
249 }
250
251 pub fn arcs(&self) -> &[(usize, usize)] {
253 &self.arcs
254 }
255
256 pub fn inputs(&self) -> &[usize] {
258 &self.inputs
259 }
260
261 pub fn outputs(&self) -> &[usize] {
263 &self.outputs
264 }
265
266 pub fn num_inputs(&self) -> usize {
268 self.inputs.len()
269 }
270
271 pub fn num_outputs(&self) -> usize {
273 self.outputs.len()
274 }
275}
276
277impl Problem for MinimumFaultDetectionTestSet {
278 const NAME: &'static str = "MinimumFaultDetectionTestSet";
279 type Solution = Vec<Vec<bool>>;
280 type Value = Min<i64>;
281
282 crate::problem_parameters![
283 ("num_arcs", num_arcs),
284 ("num_inputs", num_inputs),
285 ("num_outputs", num_outputs),
286 ("num_vertices", num_vertices),
287 ];
288
289 fn variant() -> Vec<(&'static str, &'static str)> {
290 crate::variant_params![]
291 }
292
293 fn evaluate(
294 &self,
295 solution: &Self::Solution,
296 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
297 if solution.len() != self.inputs.len()
298 || solution.iter().any(|row| row.len() != self.outputs.len())
299 {
300 return Err(crate::traits::EvaluationError::InvalidConfiguration(
301 "fault-test matrix dimensions do not match the instance".into(),
302 ));
303 }
304 Ok({
305 let mut boundary = vec![false; self.num_vertices];
306 for &input in &self.inputs {
307 boundary[input] = true;
308 }
309 for &output in &self.outputs {
310 boundary[output] = true;
311 }
312 let required_internal_vertices =
313 boundary.iter().filter(|&&is_boundary| !is_boundary).count();
314
315 let mut covered: HashSet<usize> = HashSet::new();
317 let mut count = 0usize;
318 for (idx, &selected) in solution.iter().flatten().enumerate() {
319 if selected {
320 count += 1;
321 covered.extend(
322 self.coverage[idx]
323 .iter()
324 .copied()
325 .filter(|&vertex| !boundary[vertex]),
326 );
327 }
328 }
329
330 if covered.len() == required_internal_vertices {
332 Min(Some(i64::try_from(count).map_err(|_| {
333 crate::traits::EvaluationError::IntegerOverflow(
334 "converting test-set size to i64".into(),
335 )
336 })?))
337 } else {
338 Min(None)
339 }
340 })
341 }
342}
343
344impl crate::solvers::BruteForceProblem for MinimumFaultDetectionTestSet {
345 fn dimensions(&self) -> Vec<usize> {
346 vec![2; self.inputs.len() * self.outputs.len()]
347 }
348}
349
350crate::declare_variants! {
351 default MinimumFaultDetectionTestSet => "2^(num_inputs * num_outputs)",
352}
353
354crate::register_brute_force! {
355 MinimumFaultDetectionTestSet decode |problem: &MinimumFaultDetectionTestSet, indices: Vec<usize>| if problem.num_outputs() == 0 { vec![Vec::new(); problem.num_inputs()] } else { indices.chunks(problem.num_outputs()).map(crate::config::config_to_bits).collect() },
356}
357
358#[cfg(feature = "example-db")]
359pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
360 vec![crate::example_db::specs::ModelExampleSpec {
366 id: "minimum_fault_detection_test_set",
367 instance: Box::new(MinimumFaultDetectionTestSet::new(
368 7,
369 vec![
370 (0, 2),
371 (0, 3),
372 (1, 3),
373 (1, 4),
374 (2, 5),
375 (3, 5),
376 (3, 6),
377 (4, 6),
378 ],
379 vec![0, 1],
380 vec![5, 6],
381 )),
382 optimal_config: serde_json::json!(vec![vec![true, false], vec![false, true]]),
383 optimal_value: serde_json::json!(2),
384 }]
385}
386
387#[cfg(test)]
388#[path = "../../unit_tests/models/misc/minimum_fault_detection_test_set.rs"]
389mod tests;