Skip to main content

problemreductions/models/misc/
betweenness.rs

1//! Betweenness problem implementation.
2//!
3//! Given a finite set A and a collection C of ordered triples (a, b, c),
4//! determine whether there exists a linear ordering f: A → {1, ..., |A|}
5//! such that for each (a, b, c) ∈ C, either f(a) < f(b) < f(c) or
6//! f(c) < f(b) < f(a) (i.e., b is between a and c).
7
8use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::Problem;
10use crate::types::Or;
11use serde::de::Error as _;
12use serde::{Deserialize, Deserializer, Serialize};
13
14inventory::submit! {
15    ProblemSchemaEntry {
16        name: "Betweenness",
17        display_name: "Betweenness",
18        aliases: &[],
19        dimensions: &[],
20        category: crate::registry::ProblemCategory::Misc,
21        module_path: module_path!(),
22        description: "Find a linear ordering where specified elements are between others",
23        fields: &[
24            FieldInfo { name: "num_elements", type_name: "usize", description: "Number of elements in the set A" },
25            FieldInfo { name: "triples", type_name: "Vec<(usize, usize, usize)>", description: "Collection of ordered triples (a, b, c) requiring b between a and c" },
26        ],
27    }
28}
29
30#[derive(Debug, Clone, Serialize)]
31pub struct Betweenness {
32    num_elements: usize,
33    triples: Vec<(usize, usize, usize)>,
34}
35
36impl Betweenness {
37    fn validate_inputs(
38        num_elements: usize,
39        triples: &[(usize, usize, usize)],
40    ) -> Result<(), crate::registry::ConstructionError> {
41        if num_elements == 0 {
42            return Err("Betweenness requires at least one element"
43                .to_string()
44                .into());
45        }
46        for (i, &(a, b, c)) in triples.iter().enumerate() {
47            if a >= num_elements || b >= num_elements || c >= num_elements {
48                return Err(format!(
49                    "Triple {} has element(s) out of range 0..{}",
50                    i, num_elements
51                )
52                .into());
53            }
54            if a == b || b == c || a == c {
55                return Err(
56                    format!("Triple {} has duplicate elements ({}, {}, {})", i, a, b, c).into(),
57                );
58            }
59        }
60        Ok(())
61    }
62
63    pub fn try_new(
64        num_elements: usize,
65        triples: Vec<(usize, usize, usize)>,
66    ) -> Result<Self, crate::registry::ConstructionError> {
67        Self::validate_inputs(num_elements, &triples)?;
68        Ok(Self {
69            num_elements,
70            triples,
71        })
72    }
73
74    /// Create a new Betweenness instance.
75    ///
76    /// # Panics
77    ///
78    /// Panics if any triple element is out of range or if any triple has duplicate elements.
79    pub fn new(num_elements: usize, triples: Vec<(usize, usize, usize)>) -> Self {
80        Self::try_new(num_elements, triples).unwrap_or_else(|message| panic!("{message}"))
81    }
82
83    /// Number of elements in the set A.
84    pub fn num_elements(&self) -> usize {
85        self.num_elements
86    }
87
88    /// Number of betweenness triples.
89    pub fn num_triples(&self) -> usize {
90        self.triples.len()
91    }
92
93    /// The collection of ordered triples.
94    pub fn triples(&self) -> &[(usize, usize, usize)] {
95        &self.triples
96    }
97
98    /// Check whether a configuration represents a valid permutation and
99    /// satisfies all betweenness constraints.
100    fn is_valid_solution(&self, config: &[usize]) -> bool {
101        if config.len() != self.num_elements {
102            return false;
103        }
104
105        // Check that config is a valid permutation of 0..n
106        let n = self.num_elements;
107        let mut seen = vec![false; n];
108        for &pos in config {
109            if pos >= n || seen[pos] {
110                return false;
111            }
112            seen[pos] = true;
113        }
114
115        // Check betweenness constraints: for each (a, b, c),
116        // config[a] < config[b] < config[c] OR config[c] < config[b] < config[a]
117        for &(a, b, c) in &self.triples {
118            let fa = config[a];
119            let fb = config[b];
120            let fc = config[c];
121            if !((fa < fb && fb < fc) || (fc < fb && fb < fa)) {
122                return false;
123            }
124        }
125
126        true
127    }
128}
129
130#[derive(Deserialize)]
131struct BetweennessData {
132    num_elements: usize,
133    triples: Vec<(usize, usize, usize)>,
134}
135
136impl<'de> Deserialize<'de> for Betweenness {
137    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
138    where
139        D: Deserializer<'de>,
140    {
141        let data = BetweennessData::deserialize(deserializer)?;
142        Self::try_new(data.num_elements, data.triples).map_err(D::Error::custom)
143    }
144}
145
146impl Problem for Betweenness {
147    const NAME: &'static str = "Betweenness";
148    type Solution = Vec<usize>;
149    type Value = Or;
150
151    crate::problem_parameters![("num_elements", num_elements), ("num_triples", num_triples),];
152
153    fn variant() -> Vec<(&'static str, &'static str)> {
154        crate::variant_params![]
155    }
156
157    fn evaluate(&self, config: &Self::Solution) -> Result<Or, crate::traits::EvaluationError> {
158        if config.len() != self.num_elements {
159            return Err(crate::traits::EvaluationError::InvalidConfiguration(
160                "ordering length does not match the elements".into(),
161            ));
162        }
163        if config.iter().any(|&position| position >= self.num_elements) {
164            return Err(crate::traits::EvaluationError::InvalidConfiguration(
165                "ordering contains an out-of-range position".into(),
166            ));
167        }
168        Ok(Or(self.is_valid_solution(config)))
169    }
170}
171
172impl crate::solvers::BruteForceProblem for Betweenness {
173    fn dimensions(&self) -> Vec<usize> {
174        vec![self.num_elements; self.num_elements]
175    }
176}
177
178crate::declare_variants! {
179    default Betweenness => "2^num_elements",
180}
181
182crate::register_brute_force! {
183    Betweenness,
184}
185
186#[cfg(feature = "example-db")]
187pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
188    vec![crate::example_db::specs::ModelExampleSpec {
189        id: "betweenness",
190        instance: Box::new(Betweenness::new(
191            5,
192            vec![(0, 1, 2), (2, 3, 4), (0, 2, 4), (1, 3, 4)],
193        )),
194        optimal_config: serde_json::json!(vec![0, 1, 2, 3, 4]),
195        optimal_value: serde_json::json!(true),
196    }]
197}
198
199#[cfg(test)]
200#[path = "../../unit_tests/models/misc/betweenness.rs"]
201mod tests;