Skip to main content

problemreductions/models/misc/
capacity_assignment.rs

1//! Capacity Assignment problem implementation.
2//!
3//! Capacity Assignment asks for the minimum-cost assignment of capacity levels
4//! to communication links, subject to a delay budget constraint.
5
6use crate::registry::{FieldInfo, ProblemSchemaEntry};
7use crate::traits::Problem;
8use serde::{Deserialize, Serialize};
9
10inventory::submit! {
11    ProblemSchemaEntry {
12        name: "CapacityAssignment",
13        display_name: "Capacity Assignment",
14        aliases: &[],
15        dimensions: &[],
16        module_path: module_path!(),
17        description: "Minimize total cost of capacity assignment subject to a delay budget",
18        fields: &[
19            FieldInfo { name: "capacities", type_name: "Vec<u64>", description: "Ordered capacity levels M" },
20            FieldInfo { name: "cost", type_name: "Vec<Vec<u64>>", description: "Cost matrix g(c, m) for each link and capacity" },
21            FieldInfo { name: "delay", type_name: "Vec<Vec<u64>>", description: "Delay matrix d(c, m) for each link and capacity" },
22            FieldInfo { name: "delay_budget", type_name: "u64", description: "Budget J on total delay penalty" },
23        ],
24    }
25}
26
27/// Capacity Assignment optimization problem.
28///
29/// Each variable chooses one capacity index for one communication link.
30/// Costs are monotone non-decreasing and delays are monotone non-increasing
31/// with respect to the ordered capacity list. The objective is to minimize
32/// total cost subject to a delay budget constraint.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct CapacityAssignment {
35    capacities: Vec<u64>,
36    cost: Vec<Vec<u64>>,
37    delay: Vec<Vec<u64>>,
38    delay_budget: u64,
39}
40
41impl CapacityAssignment {
42    /// Create a new Capacity Assignment instance.
43    pub fn new(
44        capacities: Vec<u64>,
45        cost: Vec<Vec<u64>>,
46        delay: Vec<Vec<u64>>,
47        delay_budget: u64,
48    ) -> Self {
49        assert!(!capacities.is_empty(), "capacities must be non-empty");
50        assert!(
51            capacities.iter().all(|&capacity| capacity > 0),
52            "capacities must be positive"
53        );
54        assert!(
55            capacities.windows(2).all(|w| w[0] < w[1]),
56            "capacities must be strictly increasing"
57        );
58        assert_eq!(
59            cost.len(),
60            delay.len(),
61            "cost and delay must have the same number of links"
62        );
63
64        let num_capacities = capacities.len();
65        for (link, row) in cost.iter().enumerate() {
66            assert_eq!(
67                row.len(),
68                num_capacities,
69                "cost row {link} length must match capacities length"
70            );
71            assert!(
72                row.windows(2).all(|w| w[0] <= w[1]),
73                "cost row {link} must be non-decreasing"
74            );
75        }
76        for (link, row) in delay.iter().enumerate() {
77            assert_eq!(
78                row.len(),
79                num_capacities,
80                "delay row {link} length must match capacities length"
81            );
82            assert!(
83                row.windows(2).all(|w| w[0] >= w[1]),
84                "delay row {link} must be non-increasing"
85            );
86        }
87
88        Self {
89            capacities,
90            cost,
91            delay,
92            delay_budget,
93        }
94    }
95
96    /// Number of communication links.
97    pub fn num_links(&self) -> usize {
98        self.cost.len()
99    }
100
101    /// Number of discrete capacity choices per link.
102    pub fn num_capacities(&self) -> usize {
103        self.capacities.len()
104    }
105
106    /// Ordered capacity levels.
107    pub fn capacities(&self) -> &[u64] {
108        &self.capacities
109    }
110
111    /// Cost matrix indexed by link, then capacity.
112    pub fn cost(&self) -> &[Vec<u64>] {
113        &self.cost
114    }
115
116    /// Delay matrix indexed by link, then capacity.
117    pub fn delay(&self) -> &[Vec<u64>] {
118        &self.delay
119    }
120
121    /// Total delay budget.
122    pub fn delay_budget(&self) -> u64 {
123        self.delay_budget
124    }
125
126    fn total_cost_and_delay(&self, config: &[usize]) -> Option<(u128, u128)> {
127        if config.len() != self.num_links() {
128            return None;
129        }
130
131        let num_capacities = self.num_capacities();
132        let mut total_cost = 0u128;
133        let mut total_delay = 0u128;
134
135        for (link, &choice) in config.iter().enumerate() {
136            if choice >= num_capacities {
137                return None;
138            }
139            total_cost += self.cost[link][choice] as u128;
140            total_delay += self.delay[link][choice] as u128;
141        }
142
143        Some((total_cost, total_delay))
144    }
145}
146
147impl Problem for CapacityAssignment {
148    const NAME: &'static str = "CapacityAssignment";
149    type Value = crate::types::Min<u128>;
150
151    fn dims(&self) -> Vec<usize> {
152        vec![self.num_capacities(); self.num_links()]
153    }
154
155    fn evaluate(&self, config: &[usize]) -> crate::types::Min<u128> {
156        let Some((total_cost, total_delay)) = self.total_cost_and_delay(config) else {
157            return crate::types::Min(None);
158        };
159        if total_delay <= self.delay_budget as u128 {
160            crate::types::Min(Some(total_cost))
161        } else {
162            crate::types::Min(None)
163        }
164    }
165
166    fn variant() -> Vec<(&'static str, &'static str)> {
167        crate::variant_params![]
168    }
169}
170
171crate::declare_variants! {
172    default CapacityAssignment => "num_capacities ^ num_links",
173}
174
175#[cfg(feature = "example-db")]
176pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
177    vec![crate::example_db::specs::ModelExampleSpec {
178        id: "capacity_assignment",
179        instance: Box::new(CapacityAssignment::new(
180            vec![1, 2, 3],
181            vec![vec![1, 3, 6], vec![2, 4, 7], vec![1, 2, 5]],
182            vec![vec![8, 4, 1], vec![7, 3, 1], vec![6, 3, 1]],
183            12,
184        )),
185        optimal_config: vec![1, 1, 1],
186        optimal_value: serde_json::json!(9),
187    }]
188}
189
190#[cfg(test)]
191#[path = "../../unit_tests/models/misc/capacity_assignment.rs"]
192mod tests;