problemreductions/models/graph/
minimum_feedback_vertex_set.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
7use crate::topology::DirectedGraph;
8use crate::traits::{OptimizationProblem, Problem};
9use crate::types::{Direction, SolutionSize, WeightElement};
10use num_traits::Zero;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "MinimumFeedbackVertexSet",
16 module_path: module_path!(),
17 description: "Find minimum weight feedback vertex set in a directed graph",
18 fields: &[
19 FieldInfo { name: "graph", type_name: "DirectedGraph", description: "The directed graph G=(V,A)" },
20 FieldInfo { name: "weights", type_name: "Vec<W>", description: "Vertex weights w: V -> R" },
21 ],
22 }
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct MinimumFeedbackVertexSet<W> {
51 graph: DirectedGraph,
53 weights: Vec<W>,
55}
56
57impl<W: Clone + Default> MinimumFeedbackVertexSet<W> {
58 pub fn new(graph: DirectedGraph, weights: Vec<W>) -> Self {
60 assert_eq!(
61 weights.len(),
62 graph.num_vertices(),
63 "weights length must match graph num_vertices"
64 );
65 Self { graph, weights }
66 }
67
68 pub fn graph(&self) -> &DirectedGraph {
70 &self.graph
71 }
72
73 pub fn weights(&self) -> &[W] {
75 &self.weights
76 }
77
78 pub fn set_weights(&mut self, weights: Vec<W>) {
80 assert_eq!(
81 weights.len(),
82 self.graph.num_vertices(),
83 "weights length must match graph num_vertices"
84 );
85 self.weights = weights;
86 }
87
88 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
90 if config.len() != self.graph.num_vertices() {
91 return false;
92 }
93 let keep: Vec<bool> = config.iter().map(|&c| c == 0).collect();
94 self.graph.induced_subgraph(&keep).is_dag()
95 }
96}
97
98impl<W: WeightElement> MinimumFeedbackVertexSet<W> {
99 pub fn is_weighted(&self) -> bool {
101 !W::IS_UNIT
102 }
103
104 pub fn num_vertices(&self) -> usize {
106 self.graph.num_vertices()
107 }
108
109 pub fn num_arcs(&self) -> usize {
111 self.graph.num_arcs()
112 }
113}
114
115impl<W> Problem for MinimumFeedbackVertexSet<W>
116where
117 W: WeightElement + crate::variant::VariantParam,
118{
119 const NAME: &'static str = "MinimumFeedbackVertexSet";
120 type Metric = SolutionSize<W::Sum>;
121
122 fn variant() -> Vec<(&'static str, &'static str)> {
123 crate::variant_params![W]
124 }
125
126 fn dims(&self) -> Vec<usize> {
127 vec![2; self.graph.num_vertices()]
128 }
129
130 fn evaluate(&self, config: &[usize]) -> SolutionSize<W::Sum> {
131 if config.len() != self.graph.num_vertices() {
132 return SolutionSize::Invalid;
133 }
134 let keep: Vec<bool> = config.iter().map(|&c| c == 0).collect();
136 let subgraph = self.graph.induced_subgraph(&keep);
137 if !subgraph.is_dag() {
138 return SolutionSize::Invalid;
139 }
140 let mut total = W::Sum::zero();
141 for (i, &selected) in config.iter().enumerate() {
142 if selected == 1 {
143 total += self.weights[i].to_sum();
144 }
145 }
146 SolutionSize::Valid(total)
147 }
148}
149
150impl<W> OptimizationProblem for MinimumFeedbackVertexSet<W>
151where
152 W: WeightElement + crate::variant::VariantParam,
153{
154 type Value = W::Sum;
155
156 fn direction(&self) -> Direction {
157 Direction::Minimize
158 }
159}
160
161crate::declare_variants! {
162 MinimumFeedbackVertexSet<i32> => "1.9977^num_vertices",
163}
164
165#[cfg(test)]
170pub(crate) fn is_feedback_vertex_set(graph: &DirectedGraph, selected: &[bool]) -> bool {
171 assert_eq!(
172 selected.len(),
173 graph.num_vertices(),
174 "selected length must match num_vertices"
175 );
176 let keep: Vec<bool> = selected.iter().map(|&s| !s).collect();
178 graph.induced_subgraph(&keep).is_dag()
179}
180
181#[cfg(test)]
182#[path = "../../unit_tests/models/graph/minimum_feedback_vertex_set.rs"]
183mod tests;