Skip to main content

problemreductions/models/graph/
multiple_copy_file_allocation.rs

1//! Multiple Copy File Allocation problem implementation.
2//!
3//! The Multiple Copy File Allocation problem asks for a placement of file copies
4//! on graph vertices that minimizes the combined storage and access cost.
5
6use 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/// Multiple Copy File Allocation problem.
27///
28/// Given an undirected graph G = (V, E), a usage value u(v) for each vertex,
29/// and a storage cost s(v) for each vertex, find a subset V' of copy vertices
30/// that minimizes:
31///
32/// Σ_{v ∈ V'} s(v) + Σ_{v ∈ V} u(v) · d(v, V')
33///
34/// where d(v, V') is the shortest-path distance from v to the nearest copy in V'.
35#[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    /// Network graph edges.
45    #[create(codec = "edge-list")]
46    graph: Vec<(usize, usize)>,
47    /// Vertex count, needed for isolated vertices.
48    num_vertices: Option<usize>,
49    /// Usage frequency per vertex.
50    #[create(codec = "comma-separated")]
51    usage: Vec<i64>,
52    /// Storage cost per vertex.
53    #[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    /// Create a new Multiple Copy File Allocation instance.
96    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    /// Get a reference to the underlying graph.
115    pub fn graph(&self) -> &SimpleGraph {
116        &self.graph
117    }
118
119    /// Get the usage values.
120    pub fn usage(&self) -> &[i64] {
121        &self.usage
122    }
123
124    /// Get the storage costs.
125    pub fn storage(&self) -> &[i64] {
126        &self.storage
127    }
128
129    /// Get the number of vertices.
130    pub fn num_vertices(&self) -> usize {
131        self.graph.num_vertices()
132    }
133
134    /// Get the number of edges.
135    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    /// Compute the total storage plus access cost for a configuration.
186    ///
187    /// Returns `None` if the configuration is not binary, has the wrong length,
188    /// selects no copy vertices, or leaves some vertex unreachable from every copy.
189    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    /// Check whether a configuration is a valid placement (at least one copy, all reachable).
240    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;