problemreductions/models/graph/
multiple_copy_file_allocation.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::types::Min;
10use serde::{Deserialize, Serialize};
11use std::collections::VecDeque;
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "MultipleCopyFileAllocation",
16 display_name: "Multiple Copy File Allocation",
17 aliases: &[],
18 dimensions: &[],
19 category: crate::registry::ProblemCategory::Graph,
20 module_path: module_path!(),
21 description: "Place file copies on graph vertices to minimize total storage plus access cost",
22 fields: MultipleCopyFileAllocationCreateSpec::FIELDS,
23 }
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct MultipleCopyFileAllocation {
37 graph: SimpleGraph,
38 usage: Vec<i64>,
39 storage: Vec<i64>,
40}
41
42#[derive(Debug, Deserialize, crate::CreateSpec)]
43struct MultipleCopyFileAllocationCreateSpec {
44 #[create(codec = "edge-list")]
46 graph: Vec<(usize, usize)>,
47 num_vertices: Option<usize>,
49 #[create(codec = "comma-separated")]
51 usage: Vec<i64>,
52 #[create(codec = "comma-separated")]
54 storage: Vec<i64>,
55}
56
57impl TryFrom<MultipleCopyFileAllocationCreateSpec> for MultipleCopyFileAllocation {
58 type Error = crate::registry::ConstructionError;
59 fn try_from(spec: MultipleCopyFileAllocationCreateSpec) -> Result<Self, Self::Error> {
60 if spec.graph.is_empty() && spec.num_vertices.is_none() {
61 return Err("num_vertices is required for an empty graph".into());
62 }
63 for &(u, v) in &spec.graph {
64 if u == v {
65 return Err(format!("self-loop {u}-{v} is not allowed").into());
66 }
67 }
68 let inferred = spec
69 .graph
70 .iter()
71 .flat_map(|&(u, v)| [u, v])
72 .max()
73 .map(|v| v.checked_add(1).ok_or("vertex count overflows usize"))
74 .transpose()?
75 .unwrap_or(0);
76 let count = spec.num_vertices.unwrap_or(inferred);
77 if count < inferred {
78 return Err("num_vertices is too small for graph endpoints".into());
79 }
80 if spec.usage.len() != count {
81 return Err("usage length must match num_vertices".into());
82 }
83 if spec.storage.len() != count {
84 return Err("storage length must match num_vertices".into());
85 }
86 Ok(Self {
87 graph: SimpleGraph::new(count, spec.graph),
88 usage: spec.usage,
89 storage: spec.storage,
90 })
91 }
92}
93
94impl MultipleCopyFileAllocation {
95 pub fn new(graph: SimpleGraph, usage: Vec<i64>, storage: Vec<i64>) -> Self {
97 assert_eq!(
98 usage.len(),
99 graph.num_vertices(),
100 "usage length must match graph num_vertices"
101 );
102 assert_eq!(
103 storage.len(),
104 graph.num_vertices(),
105 "storage length must match graph num_vertices"
106 );
107 Self {
108 graph,
109 usage,
110 storage,
111 }
112 }
113
114 pub fn graph(&self) -> &SimpleGraph {
116 &self.graph
117 }
118
119 pub fn usage(&self) -> &[i64] {
121 &self.usage
122 }
123
124 pub fn storage(&self) -> &[i64] {
126 &self.storage
127 }
128
129 pub fn num_vertices(&self) -> usize {
131 self.graph.num_vertices()
132 }
133
134 pub fn num_edges(&self) -> usize {
136 self.graph.num_edges()
137 }
138
139 fn selected_vertices(&self, config: &[bool]) -> Option<Vec<usize>> {
140 if config.len() != self.graph.num_vertices() {
141 return None;
142 }
143
144 let mut selected = Vec::new();
145 for (vertex, &selected_here) in config.iter().enumerate() {
146 if selected_here {
147 selected.push(vertex);
148 }
149 }
150
151 if selected.is_empty() {
152 None
153 } else {
154 Some(selected)
155 }
156 }
157
158 fn shortest_distances(&self, selected: &[usize]) -> Option<Vec<usize>> {
159 let n = self.graph.num_vertices();
160 let mut distances = vec![usize::MAX; n];
161 let mut queue = VecDeque::new();
162
163 for &vertex in selected {
164 distances[vertex] = 0;
165 queue.push_back(vertex);
166 }
167
168 while let Some(vertex) = queue.pop_front() {
169 let next_distance = distances[vertex] + 1;
170 for neighbor in self.graph.neighbors(vertex) {
171 if distances[neighbor] == usize::MAX {
172 distances[neighbor] = next_distance;
173 queue.push_back(neighbor);
174 }
175 }
176 }
177
178 if distances.contains(&usize::MAX) {
179 None
180 } else {
181 Some(distances)
182 }
183 }
184
185 pub fn total_cost(
190 &self,
191 config: &[bool],
192 ) -> Result<Option<i64>, crate::traits::EvaluationError> {
193 let Some(selected) = self.selected_vertices(config) else {
194 return Ok(None);
195 };
196 let Some(distances) = self.shortest_distances(&selected) else {
197 return Ok(None);
198 };
199
200 let mut storage_cost = 0_i64;
201 for vertex in selected {
202 storage_cost = storage_cost
203 .checked_add(self.storage[vertex])
204 .ok_or_else(|| {
205 crate::traits::EvaluationError::IntegerOverflow(
206 "summing file-copy storage costs".to_string(),
207 )
208 })?;
209 }
210
211 let mut access_cost = 0_i64;
212 for (vertex, distance) in distances.into_iter().enumerate() {
213 let distance = i64::try_from(distance).map_err(|_| {
214 crate::traits::EvaluationError::IntegerOverflow(
215 "converting file-copy access distance".to_string(),
216 )
217 })?;
218 let term = self.usage[vertex].checked_mul(distance).ok_or_else(|| {
219 crate::traits::EvaluationError::IntegerOverflow(
220 "multiplying file usage by access distance".to_string(),
221 )
222 })?;
223 access_cost = access_cost.checked_add(term).ok_or_else(|| {
224 crate::traits::EvaluationError::IntegerOverflow(
225 "summing file-copy access costs".to_string(),
226 )
227 })?;
228 }
229
230 Ok(Some(storage_cost.checked_add(access_cost).ok_or_else(
231 || {
232 crate::traits::EvaluationError::IntegerOverflow(
233 "summing file-copy allocation costs".to_string(),
234 )
235 },
236 )?))
237 }
238
239 pub fn is_valid_solution(
241 &self,
242 config: &[bool],
243 ) -> Result<bool, crate::traits::EvaluationError> {
244 Ok(self.total_cost(config)?.is_some())
245 }
246}
247
248impl Problem for MultipleCopyFileAllocation {
249 const NAME: &'static str = "MultipleCopyFileAllocation";
250 type Solution = Vec<bool>;
251 type Value = Min<i64>;
252
253 crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
254
255 fn variant() -> Vec<(&'static str, &'static str)> {
256 crate::variant_params![]
257 }
258
259 fn evaluate(
260 &self,
261 config: &Self::Solution,
262 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
263 if config.len() != self.graph.num_vertices() {
264 return Err(crate::traits::EvaluationError::InvalidConfiguration(
265 "copy-selection length does not match the graph vertices".into(),
266 ));
267 }
268 Ok(Min(self.total_cost(config)?))
269 }
270}
271
272impl crate::solvers::BruteForceProblem for MultipleCopyFileAllocation {
273 fn dimensions(&self) -> Vec<usize> {
274 vec![2; self.graph.num_vertices()]
275 }
276}
277
278#[cfg(feature = "example-db")]
279pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
280 vec![crate::example_db::specs::ModelExampleSpec {
281 id: "multiple_copy_file_allocation",
282 instance: Box::new(MultipleCopyFileAllocation::new(
283 SimpleGraph::new(6, vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]),
284 vec![5, 1, 1, 1, 1, 5],
285 vec![6, 2, 6, 6, 2, 6],
286 )),
287 optimal_config: serde_json::json!(vec![false, true, false, false, true, false]),
288 optimal_value: serde_json::json!(16),
289 }]
290}
291
292crate::declare_variants! {
293 default MultipleCopyFileAllocation => "2^num_vertices" create MultipleCopyFileAllocationCreateSpec,
294}
295
296crate::register_brute_force! {
297 MultipleCopyFileAllocation decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
298}
299
300#[cfg(test)]
301#[path = "../../unit_tests/models/graph/multiple_copy_file_allocation.rs"]
302mod tests;