problemreductions/models/misc/
minimum_weight_and_or_graph.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
7use crate::traits::Problem;
8use crate::types::Min;
9use serde::{Deserialize, Deserializer, Serialize};
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "MinimumWeightAndOrGraph",
14 display_name: "Minimum Weight AND/OR Graph",
15 aliases: &[],
16 dimensions: &[],
17 category: crate::registry::ProblemCategory::Misc,
18 module_path: module_path!(),
19 description: "Find the minimum-weight solution subgraph from a source in a DAG with AND/OR gates",
20 fields: MinimumWeightAndOrGraphCreateSpec::FIELDS,
21 }
22}
23
24#[derive(Debug, Clone, Serialize)]
59pub struct MinimumWeightAndOrGraph {
60 num_vertices: usize,
62 arcs: Vec<(usize, usize)>,
64 source: usize,
66 gate_types: Vec<Option<bool>>,
68 arc_weights: Vec<i64>,
70 #[serde(skip)]
72 outgoing: Vec<Vec<usize>>,
73}
74
75#[derive(Debug, Deserialize, crate::CreateSpec)]
76struct MinimumWeightAndOrGraphCreateSpec {
77 num_vertices: usize,
79 arcs: Vec<(usize, usize)>,
81 source: usize,
83 gate_types: Vec<Option<bool>>,
85 arc_weights: Option<Vec<i64>>,
87}
88impl TryFrom<MinimumWeightAndOrGraphCreateSpec> for MinimumWeightAndOrGraph {
89 type Error = crate::registry::ConstructionError;
90 fn try_from(spec: MinimumWeightAndOrGraphCreateSpec) -> Result<Self, Self::Error> {
91 if spec.source >= spec.num_vertices {
92 return Err("source is outside the graph".to_string().into());
93 }
94 if spec.gate_types.len() != spec.num_vertices {
95 return Err("gate_types length must equal num_vertices"
96 .to_string()
97 .into());
98 }
99 if spec.gate_types[spec.source].is_none() {
100 return Err("source must be an AND or OR gate".to_string().into());
101 }
102 if let Some(&(u, v)) = spec
103 .arcs
104 .iter()
105 .find(|&&(u, v)| u >= spec.num_vertices || v >= spec.num_vertices)
106 {
107 return Err(format!("arc ({u}, {v}) is out of bounds").into());
108 }
109 let count = spec.arcs.len();
110 let arc_weights = spec.arc_weights.unwrap_or_else(|| vec![1; count]);
111 if arc_weights.len() != count {
112 return Err(format!(
113 "arc_weights has {} entries, expected {count}",
114 arc_weights.len()
115 )
116 .into());
117 }
118 Ok(Self::new(
119 spec.num_vertices,
120 spec.arcs,
121 spec.source,
122 spec.gate_types,
123 arc_weights,
124 ))
125 }
126}
127
128#[derive(Deserialize)]
129struct MinimumWeightAndOrGraphData {
130 num_vertices: usize,
131 arcs: Vec<(usize, usize)>,
132 source: usize,
133 gate_types: Vec<Option<bool>>,
134 arc_weights: Vec<i64>,
135}
136
137impl<'de> Deserialize<'de> for MinimumWeightAndOrGraph {
138 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
139 where
140 D: Deserializer<'de>,
141 {
142 let data = MinimumWeightAndOrGraphData::deserialize(deserializer)?;
143 let outgoing = Self::build_outgoing(data.num_vertices, &data.arcs);
144 Ok(Self {
145 num_vertices: data.num_vertices,
146 arcs: data.arcs,
147 source: data.source,
148 gate_types: data.gate_types,
149 arc_weights: data.arc_weights,
150 outgoing,
151 })
152 }
153}
154
155impl MinimumWeightAndOrGraph {
156 pub fn new(
164 num_vertices: usize,
165 arcs: Vec<(usize, usize)>,
166 source: usize,
167 gate_types: Vec<Option<bool>>,
168 arc_weights: Vec<i64>,
169 ) -> Self {
170 assert!(
171 source < num_vertices,
172 "Source vertex {} out of bounds for {} vertices",
173 source,
174 num_vertices
175 );
176 assert_eq!(
177 gate_types.len(),
178 num_vertices,
179 "gate_types length {} does not match num_vertices {}",
180 gate_types.len(),
181 num_vertices
182 );
183 assert_eq!(
184 arc_weights.len(),
185 arcs.len(),
186 "arc_weights length {} does not match number of arcs {}",
187 arc_weights.len(),
188 arcs.len()
189 );
190 for (i, &(u, v)) in arcs.iter().enumerate() {
191 assert!(
192 u < num_vertices && v < num_vertices,
193 "Arc {} ({}, {}) out of bounds for {} vertices",
194 i,
195 u,
196 v,
197 num_vertices
198 );
199 }
200 assert!(
201 gate_types[source].is_some(),
202 "Source vertex must be an AND or OR gate, not a leaf"
203 );
204 let outgoing = Self::build_outgoing(num_vertices, &arcs);
205 Self {
206 num_vertices,
207 arcs,
208 source,
209 gate_types,
210 arc_weights,
211 outgoing,
212 }
213 }
214
215 fn build_outgoing(num_vertices: usize, arcs: &[(usize, usize)]) -> Vec<Vec<usize>> {
217 let mut outgoing = vec![vec![]; num_vertices];
218 for (i, &(u, _v)) in arcs.iter().enumerate() {
219 outgoing[u].push(i);
220 }
221 outgoing
222 }
223
224 pub fn num_vertices(&self) -> usize {
226 self.num_vertices
227 }
228
229 pub fn num_arcs(&self) -> usize {
231 self.arcs.len()
232 }
233
234 pub fn arcs(&self) -> &[(usize, usize)] {
236 &self.arcs
237 }
238
239 pub fn source(&self) -> usize {
241 self.source
242 }
243
244 pub fn gate_types(&self) -> &[Option<bool>] {
246 &self.gate_types
247 }
248
249 pub fn arc_weights(&self) -> &[i64] {
251 &self.arc_weights
252 }
253}
254
255impl Problem for MinimumWeightAndOrGraph {
256 const NAME: &'static str = "MinimumWeightAndOrGraph";
257 type Solution = Vec<bool>;
258 type Value = Min<i64>;
259
260 crate::problem_parameters![("num_arcs", num_arcs), ("num_vertices", num_vertices),];
261
262 fn variant() -> Vec<(&'static str, &'static str)> {
263 crate::variant_params![]
264 }
265
266 fn evaluate(
267 &self,
268 config: &Self::Solution,
269 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
270 Ok({
271 if config.len() != self.arcs.len() {
272 return Err(crate::traits::EvaluationError::InvalidConfiguration(
273 "arc-selection length does not match the graph".into(),
274 ));
275 }
276
277 let selected = config;
280
281 let mut solved = vec![false; self.num_vertices];
283 let mut stack = vec![self.source];
284 solved[self.source] = true;
285
286 while let Some(v) = stack.pop() {
287 match self.gate_types[v] {
288 None => {
289 }
291 Some(is_and) => {
292 let out_arcs = &self.outgoing[v];
293 let selected_out: Vec<usize> = out_arcs
294 .iter()
295 .copied()
296 .filter(|&ai| selected[ai])
297 .collect();
298
299 if is_and {
300 if selected_out.len() != out_arcs.len() {
302 return Ok(Min(None));
303 }
304 } else {
305 if selected_out.is_empty() {
307 return Ok(Min(None));
308 }
309 }
310
311 for &ai in &selected_out {
313 let (_u, child) = self.arcs[ai];
314 if !solved[child] {
315 solved[child] = true;
316 stack.push(child);
317 }
318 }
319 }
320 }
321 }
322
323 for (ai, &sel) in selected.iter().enumerate() {
325 if sel {
326 let (u, _v) = self.arcs[ai];
327 if !solved[u] {
328 return Ok(Min(None));
329 }
330 }
331 }
332
333 let total_weight = selected
335 .iter()
336 .enumerate()
337 .filter(|(_, &sel)| sel)
338 .map(|(i, _)| self.arc_weights[i])
339 .try_fold(0_i64, |total, weight| {
340 total.checked_add(weight).ok_or_else(|| {
341 crate::traits::EvaluationError::IntegerOverflow(
342 "summing selected AND/OR graph arc weights".into(),
343 )
344 })
345 })?;
346
347 Min(Some(total_weight))
348 })
349 }
350}
351
352impl crate::solvers::BruteForceProblem for MinimumWeightAndOrGraph {
353 fn dimensions(&self) -> Vec<usize> {
354 vec![2; self.arcs.len()]
355 }
356}
357
358crate::declare_variants! {
359 default MinimumWeightAndOrGraph => "2^num_arcs" create MinimumWeightAndOrGraphCreateSpec,
360}
361
362crate::register_brute_force! {
363 MinimumWeightAndOrGraph decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
364}
365
366#[cfg(feature = "example-db")]
367pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
368 vec![crate::example_db::specs::ModelExampleSpec {
390 id: "minimum_weight_and_or_graph",
391 instance: Box::new(MinimumWeightAndOrGraph::new(
392 7,
393 vec![(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)],
394 0,
395 vec![Some(true), Some(false), Some(false), None, None, None, None],
396 vec![1, 2, 3, 1, 4, 2],
397 )),
398 optimal_config: serde_json::json!(vec![true, true, false, true, false, true]),
399 optimal_value: serde_json::json!(6),
400 }]
401}
402
403#[cfg(test)]
404#[path = "../../unit_tests/models/misc/minimum_weight_and_or_graph.rs"]
405mod tests;