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 let arc_weights = spec.arc_weights.unwrap_or_else(|| vec![1; spec.arcs.len()]);
92 Self::try_new(
93 spec.num_vertices,
94 spec.arcs,
95 spec.source,
96 spec.gate_types,
97 arc_weights,
98 )
99 }
100}
101
102#[derive(Deserialize)]
103struct MinimumWeightAndOrGraphData {
104 num_vertices: usize,
105 arcs: Vec<(usize, usize)>,
106 source: usize,
107 gate_types: Vec<Option<bool>>,
108 arc_weights: Vec<i64>,
109}
110
111impl<'de> Deserialize<'de> for MinimumWeightAndOrGraph {
112 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
113 where
114 D: Deserializer<'de>,
115 {
116 let data = MinimumWeightAndOrGraphData::deserialize(deserializer)?;
117 Self::try_new(
118 data.num_vertices,
119 data.arcs,
120 data.source,
121 data.gate_types,
122 data.arc_weights,
123 )
124 .map_err(serde::de::Error::custom)
125 }
126}
127
128impl MinimumWeightAndOrGraph {
129 pub fn new(
137 num_vertices: usize,
138 arcs: Vec<(usize, usize)>,
139 source: usize,
140 gate_types: Vec<Option<bool>>,
141 arc_weights: Vec<i64>,
142 ) -> Self {
143 Self::try_new(num_vertices, arcs, source, gate_types, arc_weights)
144 .unwrap_or_else(|error| panic!("{error}"))
145 }
146
147 fn try_new(
148 num_vertices: usize,
149 arcs: Vec<(usize, usize)>,
150 source: usize,
151 gate_types: Vec<Option<bool>>,
152 arc_weights: Vec<i64>,
153 ) -> Result<Self, crate::registry::ConstructionError> {
154 if source >= num_vertices {
155 return Err(format!(
156 "Source vertex {} out of bounds for {} vertices",
157 source, num_vertices
158 )
159 .into());
160 }
161 if gate_types.len() != num_vertices {
162 return Err(format!(
163 "gate_types length {} does not match num_vertices {}",
164 gate_types.len(),
165 num_vertices
166 )
167 .into());
168 }
169 if arc_weights.len() != arcs.len() {
170 return Err(format!(
171 "arc_weights length {} does not match number of arcs {}",
172 arc_weights.len(),
173 arcs.len()
174 )
175 .into());
176 }
177 for (i, &(u, v)) in arcs.iter().enumerate() {
178 if u >= num_vertices || v >= num_vertices {
179 return Err(format!(
180 "Arc {} ({}, {}) out of bounds for {} vertices",
181 i, u, v, num_vertices
182 )
183 .into());
184 }
185 }
186 if gate_types[source].is_none() {
187 return Err("Source vertex must be an AND or OR gate, not a leaf".into());
188 }
189 let outgoing = Self::build_outgoing(num_vertices, &arcs);
190 Ok(Self {
191 num_vertices,
192 arcs,
193 source,
194 gate_types,
195 arc_weights,
196 outgoing,
197 })
198 }
199
200 fn build_outgoing(num_vertices: usize, arcs: &[(usize, usize)]) -> Vec<Vec<usize>> {
202 let mut outgoing = vec![vec![]; num_vertices];
203 for (i, &(u, _v)) in arcs.iter().enumerate() {
204 outgoing[u].push(i);
205 }
206 outgoing
207 }
208
209 pub fn num_vertices(&self) -> usize {
211 self.num_vertices
212 }
213
214 pub fn num_arcs(&self) -> usize {
216 self.arcs.len()
217 }
218
219 pub fn arcs(&self) -> &[(usize, usize)] {
221 &self.arcs
222 }
223
224 pub fn source(&self) -> usize {
226 self.source
227 }
228
229 pub fn gate_types(&self) -> &[Option<bool>] {
231 &self.gate_types
232 }
233
234 pub fn arc_weights(&self) -> &[i64] {
236 &self.arc_weights
237 }
238}
239
240impl Problem for MinimumWeightAndOrGraph {
241 const NAME: &'static str = "MinimumWeightAndOrGraph";
242 type Solution = Vec<bool>;
243 type Value = Min<i64>;
244
245 crate::problem_parameters![("num_arcs", num_arcs), ("num_vertices", num_vertices),];
246
247 fn variant() -> Vec<(&'static str, &'static str)> {
248 crate::variant_params![]
249 }
250
251 fn evaluate(
252 &self,
253 config: &Self::Solution,
254 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
255 Ok({
256 if config.len() != self.arcs.len() {
257 return Err(crate::traits::EvaluationError::InvalidConfiguration(
258 "arc-selection length does not match the graph".into(),
259 ));
260 }
261
262 let selected = config;
265
266 let mut solved = vec![false; self.num_vertices];
268 let mut stack = vec![self.source];
269 solved[self.source] = true;
270
271 while let Some(v) = stack.pop() {
272 match self.gate_types[v] {
273 None => {
274 }
276 Some(is_and) => {
277 let out_arcs = &self.outgoing[v];
278 let selected_out: Vec<usize> = out_arcs
279 .iter()
280 .copied()
281 .filter(|&ai| selected[ai])
282 .collect();
283
284 if is_and {
285 if selected_out.len() != out_arcs.len() {
287 return Ok(Min(None));
288 }
289 } else {
290 if selected_out.is_empty() {
292 return Ok(Min(None));
293 }
294 }
295
296 for &ai in &selected_out {
298 let (_u, child) = self.arcs[ai];
299 if !solved[child] {
300 solved[child] = true;
301 stack.push(child);
302 }
303 }
304 }
305 }
306 }
307
308 for (ai, &sel) in selected.iter().enumerate() {
310 if sel {
311 let (u, _v) = self.arcs[ai];
312 if !solved[u] {
313 return Ok(Min(None));
314 }
315 }
316 }
317
318 let total_weight = selected
320 .iter()
321 .enumerate()
322 .filter(|(_, &sel)| sel)
323 .map(|(i, _)| self.arc_weights[i])
324 .try_fold(0_i64, |total, weight| {
325 total.checked_add(weight).ok_or_else(|| {
326 crate::traits::EvaluationError::IntegerOverflow(
327 "summing selected AND/OR graph arc weights".into(),
328 )
329 })
330 })?;
331
332 Min(Some(total_weight))
333 })
334 }
335}
336
337impl crate::solvers::BruteForceProblem for MinimumWeightAndOrGraph {
338 fn dimensions(&self) -> Vec<usize> {
339 vec![2; self.arcs.len()]
340 }
341}
342
343crate::declare_variants! {
344 default MinimumWeightAndOrGraph => "2^num_arcs" create MinimumWeightAndOrGraphCreateSpec,
345}
346
347crate::register_brute_force! {
348 MinimumWeightAndOrGraph decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
349}
350
351#[cfg(feature = "example-db")]
352pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
353 vec![crate::example_db::specs::ModelExampleSpec {
375 id: "minimum_weight_and_or_graph",
376 instance: Box::new(MinimumWeightAndOrGraph::new(
377 7,
378 vec![(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)],
379 0,
380 vec![Some(true), Some(false), Some(false), None, None, None, None],
381 vec![1, 2, 3, 1, 4, 2],
382 )),
383 optimal_config: serde_json::json!(vec![true, true, false, true, false, true]),
384 optimal_value: serde_json::json!(6),
385 }]
386}
387
388#[cfg(test)]
389#[path = "../../unit_tests/models/misc/minimum_weight_and_or_graph.rs"]
390mod tests;