problemreductions/models/misc/
capacity_assignment.rs1use 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#[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 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 pub fn num_links(&self) -> usize {
98 self.cost.len()
99 }
100
101 pub fn num_capacities(&self) -> usize {
103 self.capacities.len()
104 }
105
106 pub fn capacities(&self) -> &[u64] {
108 &self.capacities
109 }
110
111 pub fn cost(&self) -> &[Vec<u64>] {
113 &self.cost
114 }
115
116 pub fn delay(&self) -> &[Vec<u64>] {
118 &self.delay
119 }
120
121 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;