problemreductions/models/graph/
minimum_feedback_vertex_set.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
7use crate::topology::DirectedGraph;
8use crate::traits::Problem;
9use crate::types::{Min, One, WeightElement};
10use num_traits::Zero;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "MinimumFeedbackVertexSet",
16 display_name: "Minimum Feedback Vertex Set",
17 aliases: &["FVS"],
18 dimensions: &[
19 VariantDimension::new("weight", "i64", &["i64", "One"]),
20 ],
21 category: crate::registry::ProblemCategory::Graph,
22 module_path: module_path!(),
23 description: "Find minimum weight feedback vertex set in a directed graph",
24 fields: MinimumFeedbackVertexSetCreateSpec::<i64>::FIELDS,
25 }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct MinimumFeedbackVertexSet<W> {
54 graph: DirectedGraph,
56 weights: Vec<W>,
58}
59
60#[derive(Debug, Deserialize, crate::CreateSpec)]
61struct MinimumFeedbackVertexSetCreateSpec<W> {
62 graph: DirectedGraph,
64 weights: Option<Vec<W>>,
66}
67impl<W: WeightElement> TryFrom<MinimumFeedbackVertexSetCreateSpec<W>>
68 for MinimumFeedbackVertexSet<W>
69{
70 type Error = crate::registry::ConstructionError;
71 fn try_from(spec: MinimumFeedbackVertexSetCreateSpec<W>) -> Result<Self, Self::Error> {
72 let count = spec.graph.num_vertices();
73 let weights = spec.weights.unwrap_or_else(|| vec![W::unit(); count]);
74 if weights.len() != count {
75 return Err(format!("weights has {} entries, expected {count}", weights.len()).into());
76 }
77 Ok(Self::new(spec.graph, weights))
78 }
79}
80
81impl<W: Clone + Default> MinimumFeedbackVertexSet<W> {
82 pub fn new(graph: DirectedGraph, weights: Vec<W>) -> Self {
84 assert_eq!(
85 weights.len(),
86 graph.num_vertices(),
87 "weights length must match graph num_vertices"
88 );
89 Self { graph, weights }
90 }
91
92 pub fn graph(&self) -> &DirectedGraph {
94 &self.graph
95 }
96
97 pub fn weights(&self) -> &[W] {
99 &self.weights
100 }
101
102 pub fn set_weights(&mut self, weights: Vec<W>) {
104 assert_eq!(
105 weights.len(),
106 self.graph.num_vertices(),
107 "weights length must match graph num_vertices"
108 );
109 self.weights = weights;
110 }
111
112 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
114 if config.len() != self.graph.num_vertices() {
115 return false;
116 }
117 let keep: Vec<bool> = config.iter().map(|&c| c == 0).collect();
118 self.graph.induced_subgraph(&keep).is_dag()
119 }
120}
121
122impl<W: WeightElement> MinimumFeedbackVertexSet<W> {
123 pub fn is_weighted(&self) -> bool {
125 !W::IS_UNIT
126 }
127
128 pub fn num_vertices(&self) -> usize {
130 self.graph.num_vertices()
131 }
132
133 pub fn num_arcs(&self) -> usize {
135 self.graph.num_arcs()
136 }
137}
138
139impl<W> Problem for MinimumFeedbackVertexSet<W>
140where
141 W: WeightElement + crate::variant::VariantParam,
142{
143 const NAME: &'static str = "MinimumFeedbackVertexSet";
144 type Solution = Vec<bool>;
145 type Value = Min<W::Sum>;
146
147 crate::problem_parameters![("num_arcs", num_arcs), ("num_vertices", num_vertices),];
148
149 fn variant() -> Vec<(&'static str, &'static str)> {
150 crate::variant_params![W]
151 }
152
153 fn evaluate(
154 &self,
155 config: &Self::Solution,
156 ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
157 Ok({
158 if config.len() != self.graph.num_vertices() {
159 return Err(crate::traits::EvaluationError::InvalidConfiguration(
160 "vertex-selection length does not match the graph".into(),
161 ));
162 }
163 let keep: Vec<bool> = config.iter().map(|&removed| !removed).collect();
165 let subgraph = self.graph.induced_subgraph(&keep);
166 if !subgraph.is_dag() {
167 return Ok(Min(None));
168 }
169 let mut total = W::Sum::zero();
170 for (i, &selected) in config.iter().enumerate() {
171 if selected {
172 total = W::checked_add_to_sum(
173 total,
174 self.weights[i].to_sum(),
175 "summing selected feedback-vertex weights",
176 )?;
177 }
178 }
179 Min(Some(total))
180 })
181 }
182}
183
184impl<W> crate::solvers::BruteForceProblem for MinimumFeedbackVertexSet<W>
185where
186 W: WeightElement + crate::variant::VariantParam,
187{
188 fn dimensions(&self) -> Vec<usize> {
189 vec![2; self.graph.num_vertices()]
190 }
191}
192
193#[derive(Debug, Deserialize, crate::CreateSpec)]
194struct MinimumFeedbackVertexSetOneCreateSpec {
195 graph: DirectedGraph,
197}
198
199impl TryFrom<MinimumFeedbackVertexSetOneCreateSpec> for MinimumFeedbackVertexSet<One> {
200 type Error = crate::registry::ConstructionError;
201 fn try_from(spec: MinimumFeedbackVertexSetOneCreateSpec) -> Result<Self, Self::Error> {
202 let weights = vec![One; spec.graph.num_vertices()];
203 Ok(Self::new(spec.graph, weights))
204 }
205}
206
207crate::declare_variants! {
208 default MinimumFeedbackVertexSet<i64> => "1.9977^num_vertices" create MinimumFeedbackVertexSetCreateSpec<i64>,
209 MinimumFeedbackVertexSet<One> => "1.9977^num_vertices" create MinimumFeedbackVertexSetOneCreateSpec,
210}
211
212crate::register_brute_force! {
213 MinimumFeedbackVertexSet<i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
214 MinimumFeedbackVertexSet<One> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
215}
216
217#[cfg(feature = "example-db")]
218pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
219 use crate::topology::DirectedGraph;
220 vec![
221 crate::example_db::specs::ModelExampleSpec {
222 id: "minimum_feedback_vertex_set",
223 instance: Box::new(MinimumFeedbackVertexSet::new(
224 DirectedGraph::new(
225 5,
226 vec![(0, 1), (1, 2), (2, 0), (0, 3), (3, 4), (4, 1), (4, 2)],
227 ),
228 vec![1i64; 5],
229 )),
230 optimal_config: serde_json::json!(vec![true, false, false, false, false]),
231 optimal_value: serde_json::json!(1),
232 },
233 crate::example_db::specs::ModelExampleSpec {
234 id: "minimum_feedback_vertex_set_unit",
235 instance: Box::new(MinimumFeedbackVertexSet::new(
236 DirectedGraph::new(3, vec![(0, 1), (1, 2), (2, 0)]),
237 vec![One; 3],
238 )),
239 optimal_config: serde_json::json!(vec![true, false, false]),
240 optimal_value: serde_json::json!(1),
241 },
242 ]
243}
244
245#[cfg(test)]
250pub(crate) fn is_feedback_vertex_set(graph: &DirectedGraph, selected: &[bool]) -> bool {
251 assert_eq!(
252 selected.len(),
253 graph.num_vertices(),
254 "selected length must match num_vertices"
255 );
256 let keep: Vec<bool> = selected.iter().map(|&s| !s).collect();
258 graph.induced_subgraph(&keep).is_dag()
259}
260
261#[cfg(test)]
262#[path = "../../unit_tests/models/graph/minimum_feedback_vertex_set.rs"]
263mod tests;