problemreductions/models/graph/
minimum_feedback_arc_set.rs1use crate::registry::{CreateSpec, 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", "i64", &["i64"]),
20 ],
21 category: crate::registry::ProblemCategory::Graph,
22 module_path: module_path!(),
23 description: "Find minimum weight feedback arc set in a directed graph",
24 fields: MinimumFeedbackArcSetCreateSpec::FIELDS,
25 }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct MinimumFeedbackArcSet<W> {
60 graph: DirectedGraph,
62 weights: Vec<W>,
64}
65
66#[derive(Debug, Deserialize, crate::CreateSpec)]
67struct MinimumFeedbackArcSetCreateSpec {
68 graph: DirectedGraph,
70 weights: Option<Vec<i64>>,
72}
73impl TryFrom<MinimumFeedbackArcSetCreateSpec> for MinimumFeedbackArcSet<i64> {
74 type Error = crate::registry::ConstructionError;
75 fn try_from(spec: MinimumFeedbackArcSetCreateSpec) -> Result<Self, Self::Error> {
76 let count = spec.graph.num_arcs();
77 let weights = spec.weights.unwrap_or_else(|| vec![1; count]);
78 if weights.len() != count {
79 return Err(format!("weights has {} entries, expected {count}", weights.len()).into());
80 }
81 Ok(Self::new(spec.graph, weights))
82 }
83}
84
85impl<W: Clone + Default> MinimumFeedbackArcSet<W> {
86 pub fn new(graph: DirectedGraph, weights: Vec<W>) -> Self {
88 assert_eq!(
89 weights.len(),
90 graph.num_arcs(),
91 "weights length must match graph num_arcs"
92 );
93 Self { graph, weights }
94 }
95
96 pub fn graph(&self) -> &DirectedGraph {
98 &self.graph
99 }
100
101 pub fn weights(&self) -> &[W] {
103 &self.weights
104 }
105
106 pub fn set_weights(&mut self, weights: Vec<W>) {
108 assert_eq!(
109 weights.len(),
110 self.graph.num_arcs(),
111 "weights length must match graph num_arcs"
112 );
113 self.weights = weights;
114 }
115
116 pub fn is_valid_solution(&self, config: &[bool]) -> bool {
120 is_valid_fas(&self.graph, config)
121 }
122}
123
124impl<W: WeightElement> MinimumFeedbackArcSet<W> {
125 pub fn is_weighted(&self) -> bool {
127 !W::IS_UNIT
128 }
129
130 pub fn num_vertices(&self) -> usize {
132 self.graph.num_vertices()
133 }
134
135 pub fn num_arcs(&self) -> usize {
137 self.graph.num_arcs()
138 }
139}
140
141impl<W> Problem for MinimumFeedbackArcSet<W>
142where
143 W: WeightElement + crate::variant::VariantParam,
144{
145 const NAME: &'static str = "MinimumFeedbackArcSet";
146 type Solution = Vec<bool>;
147 type Value = Min<W::Sum>;
148
149 crate::problem_parameters![("num_arcs", num_arcs), ("num_vertices", num_vertices),];
150
151 fn variant() -> Vec<(&'static str, &'static str)> {
152 crate::variant_params![W]
153 }
154
155 fn evaluate(
156 &self,
157 config: &Self::Solution,
158 ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
159 if config.len() != self.graph.num_arcs() {
160 return Err(crate::traits::EvaluationError::InvalidConfiguration(
161 "arc-selection length does not match the graph".into(),
162 ));
163 }
164 Ok({
165 if !is_valid_fas(&self.graph, config) {
166 return Ok(Min(None));
167 }
168 let mut total = W::Sum::zero();
169 for (i, &selected) in config.iter().enumerate() {
170 if selected {
171 total = W::checked_add_to_sum(
172 total,
173 self.weights[i].to_sum(),
174 "summing selected feedback-arc weights",
175 )?;
176 }
177 }
178 Min(Some(total))
179 })
180 }
181}
182
183impl<W> crate::solvers::BruteForceProblem for MinimumFeedbackArcSet<W>
184where
185 W: WeightElement + crate::variant::VariantParam,
186{
187 fn dimensions(&self) -> Vec<usize> {
188 vec![2; self.graph.num_arcs()]
189 }
190}
191
192fn is_valid_fas(graph: &DirectedGraph, config: &[bool]) -> bool {
197 let num_arcs = graph.num_arcs();
198 if config.len() != num_arcs {
199 return false;
200 }
201 let kept_arcs: Vec<bool> = config.iter().map(|&removed| !removed).collect();
203 graph.is_acyclic_subgraph(&kept_arcs)
204}
205
206crate::declare_variants! {
207 default MinimumFeedbackArcSet<i64> => "2^num_vertices" create MinimumFeedbackArcSetCreateSpec,
208}
209
210crate::register_brute_force! {
211 MinimumFeedbackArcSet<i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
212}
213
214#[cfg(feature = "example-db")]
215pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
216 use crate::topology::DirectedGraph;
217 vec![crate::example_db::specs::ModelExampleSpec {
219 id: "minimum_feedback_arc_set",
220 instance: Box::new(MinimumFeedbackArcSet::new(
221 DirectedGraph::new(3, vec![(0, 1), (1, 2), (2, 0)]),
222 vec![1i64, 1, 1],
223 )),
224 optimal_config: serde_json::json!(vec![false, false, true]),
225 optimal_value: serde_json::json!(1),
226 }]
227}
228
229#[cfg(test)]
230#[path = "../../unit_tests/models/graph/minimum_feedback_arc_set.rs"]
231mod tests;