problemreductions/models/graph/
minimum_feedback_arc_set.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
7use crate::topology::DirectedGraph;
8use crate::traits::Problem;
9use crate::types::{Min, WeightElement};
10use num_traits::Zero;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "MinimumFeedbackArcSet",
16 display_name: "Minimum Feedback Arc Set",
17 aliases: &["FAS"],
18 dimensions: &[
19 VariantDimension::new("weight", "i32", &["i32"]),
20 ],
21 module_path: module_path!(),
22 description: "Find minimum weight feedback arc set in a directed graph",
23 fields: &[
24 FieldInfo { name: "graph", type_name: "DirectedGraph", description: "The directed graph G=(V,A)" },
25 FieldInfo { name: "weights", type_name: "Vec<W>", description: "Arc weights w: A -> R" },
26 ],
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct MinimumFeedbackArcSet<W> {
62 graph: DirectedGraph,
64 weights: Vec<W>,
66}
67
68impl<W: Clone + Default> MinimumFeedbackArcSet<W> {
69 pub fn new(graph: DirectedGraph, weights: Vec<W>) -> Self {
71 assert_eq!(
72 weights.len(),
73 graph.num_arcs(),
74 "weights length must match graph num_arcs"
75 );
76 Self { graph, weights }
77 }
78
79 pub fn graph(&self) -> &DirectedGraph {
81 &self.graph
82 }
83
84 pub fn weights(&self) -> &[W] {
86 &self.weights
87 }
88
89 pub fn set_weights(&mut self, weights: Vec<W>) {
91 assert_eq!(
92 weights.len(),
93 self.graph.num_arcs(),
94 "weights length must match graph num_arcs"
95 );
96 self.weights = weights;
97 }
98
99 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
103 is_valid_fas(&self.graph, config)
104 }
105}
106
107impl<W: WeightElement> MinimumFeedbackArcSet<W> {
108 pub fn is_weighted(&self) -> bool {
110 !W::IS_UNIT
111 }
112
113 pub fn num_vertices(&self) -> usize {
115 self.graph.num_vertices()
116 }
117
118 pub fn num_arcs(&self) -> usize {
120 self.graph.num_arcs()
121 }
122}
123
124impl<W> Problem for MinimumFeedbackArcSet<W>
125where
126 W: WeightElement + crate::variant::VariantParam,
127{
128 const NAME: &'static str = "MinimumFeedbackArcSet";
129 type Value = Min<W::Sum>;
130
131 fn variant() -> Vec<(&'static str, &'static str)> {
132 crate::variant_params![W]
133 }
134
135 fn dims(&self) -> Vec<usize> {
136 vec![2; self.graph.num_arcs()]
137 }
138
139 fn evaluate(&self, config: &[usize]) -> Min<W::Sum> {
140 if !is_valid_fas(&self.graph, config) {
141 return Min(None);
142 }
143 let mut total = W::Sum::zero();
144 for (i, &selected) in config.iter().enumerate() {
145 if selected != 0 {
146 total += self.weights[i].to_sum();
147 }
148 }
149 Min(Some(total))
150 }
151}
152
153fn is_valid_fas(graph: &DirectedGraph, config: &[usize]) -> bool {
158 let num_arcs = graph.num_arcs();
159 if config.len() != num_arcs {
160 return false;
161 }
162 let kept_arcs: Vec<bool> = config.iter().map(|&x| x == 0).collect();
164 graph.is_acyclic_subgraph(&kept_arcs)
165}
166
167crate::declare_variants! {
168 default MinimumFeedbackArcSet<i32> => "2^num_vertices",
169}
170
171#[cfg(feature = "example-db")]
172pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
173 use crate::topology::DirectedGraph;
174 vec![crate::example_db::specs::ModelExampleSpec {
176 id: "minimum_feedback_arc_set",
177 instance: Box::new(MinimumFeedbackArcSet::new(
178 DirectedGraph::new(3, vec![(0, 1), (1, 2), (2, 0)]),
179 vec![1i32, 1, 1],
180 )),
181 optimal_config: vec![0, 0, 1],
182 optimal_value: serde_json::json!(1),
183 }]
184}
185
186#[cfg(test)]
187#[path = "../../unit_tests/models/graph/minimum_feedback_arc_set.rs"]
188mod tests;