problemreductions/models/graph/
multiple_choice_branching.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
8use crate::topology::DirectedGraph;
9use crate::traits::Problem;
10use crate::types::WeightElement;
11use num_traits::Zero;
12use serde::de::Error as _;
13use serde::{Deserialize, Serialize};
14
15inventory::submit! {
16 ProblemSchemaEntry {
17 name: "MultipleChoiceBranching",
18 display_name: "Multiple Choice Branching",
19 aliases: &[],
20 dimensions: &[
21 VariantDimension::new("weight", "i64", &["i64"]),
22 ],
23 category: crate::registry::ProblemCategory::Graph,
24 module_path: module_path!(),
25 description: "Find a branching with partition constraints and weight at least K",
26 fields: MultipleChoiceBranchingCreateSpec::FIELDS,
27 }
28}
29
30#[derive(Debug, Clone, Serialize)]
40pub struct MultipleChoiceBranching<W: WeightElement> {
41 graph: DirectedGraph,
42 weights: Vec<W>,
43 partition: Vec<Vec<usize>>,
44 threshold: W::Sum,
45}
46
47#[derive(Debug, Deserialize, crate::CreateSpec)]
48struct MultipleChoiceBranchingCreateSpec {
49 #[create(codec = "arc-list")]
51 arcs: Vec<(usize, usize)>,
52 num_vertices: Option<usize>,
54 weights: Vec<i64>,
56 partition: Vec<Vec<usize>>,
58 threshold: i64,
60}
61
62impl TryFrom<MultipleChoiceBranchingCreateSpec> for MultipleChoiceBranching<i64> {
63 type Error = crate::registry::ConstructionError;
64 fn try_from(spec: MultipleChoiceBranchingCreateSpec) -> Result<Self, Self::Error> {
65 let inferred = spec
66 .arcs
67 .iter()
68 .flat_map(|&(u, v)| [u, v])
69 .max()
70 .map(|vertex| vertex.checked_add(1).ok_or("vertex count overflows usize"))
71 .transpose()?
72 .unwrap_or(0);
73 let num_vertices = spec.num_vertices.unwrap_or(inferred);
74 if num_vertices < inferred {
75 return Err("num_vertices is too small for arc endpoints"
76 .to_string()
77 .into());
78 }
79 let graph = DirectedGraph::new(num_vertices, spec.arcs);
80 let num_arcs = graph.num_arcs();
81 if spec.weights.len() != num_arcs {
82 return Err(format!(
83 "weights has {} entries, expected {num_arcs}",
84 spec.weights.len()
85 )
86 .into());
87 }
88 if let Some(message) = partition_validation_error(&spec.partition, num_arcs) {
89 return Err(message.into());
90 }
91 Ok(Self::new(
92 graph,
93 spec.weights,
94 spec.partition,
95 spec.threshold,
96 ))
97 }
98}
99
100#[derive(Debug, Deserialize)]
101struct MultipleChoiceBranchingUnchecked<W: WeightElement> {
102 graph: DirectedGraph,
103 weights: Vec<W>,
104 partition: Vec<Vec<usize>>,
105 threshold: W::Sum,
106}
107
108impl<'de, W> Deserialize<'de> for MultipleChoiceBranching<W>
109where
110 W: WeightElement + Deserialize<'de>,
111 W::Sum: Deserialize<'de>,
112{
113 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
114 where
115 D: serde::Deserializer<'de>,
116 {
117 let unchecked = MultipleChoiceBranchingUnchecked::<W>::deserialize(deserializer)?;
118 let num_arcs = unchecked.graph.num_arcs();
119 if unchecked.weights.len() != num_arcs {
120 return Err(D::Error::custom(format!(
121 "weights length must match graph num_arcs (expected {num_arcs}, got {})",
122 unchecked.weights.len()
123 )));
124 }
125 if let Some(message) = partition_validation_error(&unchecked.partition, num_arcs) {
126 return Err(D::Error::custom(message));
127 }
128
129 Ok(Self {
130 graph: unchecked.graph,
131 weights: unchecked.weights,
132 partition: unchecked.partition,
133 threshold: unchecked.threshold,
134 })
135 }
136}
137
138impl<W: WeightElement> MultipleChoiceBranching<W> {
139 pub fn new(
141 graph: DirectedGraph,
142 weights: Vec<W>,
143 partition: Vec<Vec<usize>>,
144 threshold: W::Sum,
145 ) -> Self {
146 let num_arcs = graph.num_arcs();
147 assert_eq!(
148 weights.len(),
149 num_arcs,
150 "weights length must match graph num_arcs"
151 );
152 validate_partition(&partition, num_arcs);
153 Self {
154 graph,
155 weights,
156 partition,
157 threshold,
158 }
159 }
160
161 pub fn graph(&self) -> &DirectedGraph {
163 &self.graph
164 }
165
166 pub fn weights(&self) -> &[W] {
168 &self.weights
169 }
170
171 pub fn set_weights(&mut self, weights: Vec<W>) {
173 assert_eq!(
174 weights.len(),
175 self.graph.num_arcs(),
176 "weights length must match graph num_arcs"
177 );
178 self.weights = weights;
179 }
180
181 pub fn is_weighted(&self) -> bool {
183 !W::IS_UNIT
184 }
185
186 pub fn partition(&self) -> &[Vec<usize>] {
188 &self.partition
189 }
190
191 pub fn threshold(&self) -> &W::Sum {
193 &self.threshold
194 }
195
196 pub fn num_vertices(&self) -> usize {
198 self.graph.num_vertices()
199 }
200
201 pub fn num_arcs(&self) -> usize {
203 self.graph.num_arcs()
204 }
205
206 pub fn num_partition_groups(&self) -> usize {
208 self.partition.len()
209 }
210
211 pub fn is_valid_solution(
213 &self,
214 config: &[bool],
215 ) -> Result<bool, crate::traits::EvaluationError> {
216 is_valid_multiple_choice_branching(
217 &self.graph,
218 &self.weights,
219 &self.partition,
220 &self.threshold,
221 config,
222 )
223 }
224}
225
226impl<W> Problem for MultipleChoiceBranching<W>
227where
228 W: WeightElement + crate::variant::VariantParam,
229{
230 const NAME: &'static str = "MultipleChoiceBranching";
231 type Solution = Vec<bool>;
232 type Value = crate::types::Or;
233
234 crate::problem_parameters![
235 ("num_vertices", num_vertices),
236 ("num_arcs", num_arcs),
237 ("num_partition_groups", num_partition_groups),
238 ];
239
240 fn variant() -> Vec<(&'static str, &'static str)> {
241 crate::variant_params![W]
242 }
243
244 fn evaluate(
245 &self,
246 config: &Self::Solution,
247 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
248 if config.len() != self.graph.num_arcs() {
249 return Err(crate::traits::EvaluationError::InvalidConfiguration(
250 "arc-selection length does not match the graph".into(),
251 ));
252 }
253 Ok({
254 crate::types::Or({
255 is_valid_multiple_choice_branching(
256 &self.graph,
257 &self.weights,
258 &self.partition,
259 &self.threshold,
260 config,
261 )?
262 })
263 })
264 }
265}
266
267impl<W> crate::solvers::BruteForceProblem for MultipleChoiceBranching<W>
268where
269 W: WeightElement + crate::variant::VariantParam,
270{
271 fn dimensions(&self) -> Vec<usize> {
272 vec![2; self.graph.num_arcs()]
273 }
274}
275
276fn validate_partition(partition: &[Vec<usize>], num_arcs: usize) {
277 if let Some(message) = partition_validation_error(partition, num_arcs) {
278 panic!("{message}");
279 }
280}
281
282fn partition_validation_error(partition: &[Vec<usize>], num_arcs: usize) -> Option<String> {
283 let mut seen = vec![false; num_arcs];
284 for group in partition {
285 for &arc_index in group {
286 if arc_index >= num_arcs {
287 return Some(format!(
288 "partition arc index {} out of range for {} arcs",
289 arc_index, num_arcs
290 ));
291 }
292 if seen[arc_index] {
293 return Some(format!(
294 "partition arc index {} appears more than once",
295 arc_index
296 ));
297 }
298 seen[arc_index] = true;
299 }
300 }
301 if seen.iter().all(|present| *present) {
302 None
303 } else {
304 Some("partition must cover every arc exactly once".to_string())
305 }
306}
307
308fn is_valid_multiple_choice_branching<W: WeightElement>(
309 graph: &DirectedGraph,
310 weights: &[W],
311 partition: &[Vec<usize>],
312 threshold: &W::Sum,
313 config: &[bool],
314) -> Result<bool, crate::traits::EvaluationError> {
315 if config.len() != graph.num_arcs() {
316 return Ok(false);
317 }
318 for group in partition {
319 if group.iter().filter(|&&arc_index| config[arc_index]).count() > 1 {
320 return Ok(false);
321 }
322 }
323
324 let arcs = graph.arcs();
325 let mut in_degree = vec![0usize; graph.num_vertices()];
326 let mut selected_successors = vec![Vec::new(); graph.num_vertices()];
327 let mut total = W::Sum::zero();
328 for (index, &selected) in config.iter().enumerate() {
329 if selected {
330 let (source, target) = arcs[index];
331 in_degree[target] += 1;
332 if in_degree[target] > 1 {
333 return Ok(false);
334 }
335 selected_successors[source].push(target);
336 total = W::checked_add_to_sum(
337 total,
338 weights[index].to_sum(),
339 "summing multiple-choice branching weights",
340 )?;
341 }
342 }
343
344 if total < *threshold {
345 return Ok(false);
346 }
347
348 let mut queue: Vec<usize> = (0..graph.num_vertices())
349 .filter(|&vertex| in_degree[vertex] == 0)
350 .collect();
351 let mut visited = 0usize;
352 while let Some(source) = queue.pop() {
353 visited += 1;
354 for &target in &selected_successors[source] {
355 in_degree[target] -= 1;
356 if in_degree[target] == 0 {
357 queue.push(target);
358 }
359 }
360 }
361
362 Ok(visited == graph.num_vertices())
363}
364
365crate::declare_variants! {
366 default MultipleChoiceBranching<i64> => "2^num_arcs" create MultipleChoiceBranchingCreateSpec,
367}
368
369crate::register_brute_force! {
370 MultipleChoiceBranching<i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
371}
372
373#[cfg(feature = "example-db")]
374pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
375 vec![crate::example_db::specs::ModelExampleSpec {
376 id: "multiple_choice_branching",
377 instance: Box::new(MultipleChoiceBranching::new(
378 DirectedGraph::new(
379 6,
380 vec![
381 (0, 1),
382 (0, 2),
383 (1, 3),
384 (2, 3),
385 (1, 4),
386 (3, 5),
387 (4, 5),
388 (2, 4),
389 ],
390 ),
391 vec![3, 2, 4, 1, 2, 3, 1, 3],
392 vec![vec![0, 1], vec![2, 3], vec![4, 7], vec![5, 6]],
393 10,
394 )),
395 optimal_config: serde_json::json!(vec![true, false, true, false, false, true, false, true]),
396 optimal_value: serde_json::json!(true),
397 }]
398}
399
400#[cfg(test)]
401#[path = "../../unit_tests/models/graph/multiple_choice_branching.rs"]
402mod tests;