problemreductions/rules/
expectedretrievalcost_ilp.rs1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
18use crate::models::misc::ExpectedRetrievalCost;
19use crate::reduction;
20use crate::rules::ilp_helpers::{mccormick_product, one_hot_decode_rows};
21use crate::rules::traits::{ReduceTo, ReductionResult};
22
23fn latency_distance(num_sectors: usize, source: usize, target: usize) -> usize {
28 if source < target {
29 target - source - 1
30 } else {
31 num_sectors - source + target - 1
32 }
33}
34
35#[derive(Debug, Clone)]
43pub struct ReductionERCToILP {
44 target: ILP<bool, f64>,
45 num_records: usize,
46 num_sectors: usize,
47}
48
49impl ReductionERCToILP {
50 fn x_var(&self, r: usize, s: usize) -> usize {
51 r * self.num_sectors + s
52 }
53
54 fn z_var(&self, r: usize, s: usize, r2: usize, s2: usize) -> usize {
55 let n = self.num_records * self.num_sectors;
56 n + (r * self.num_sectors + s) * n + (r2 * self.num_sectors + s2)
57 }
58}
59
60impl ReductionResult for ReductionERCToILP {
61 type Source = ExpectedRetrievalCost;
62 type Target = ILP<bool, f64>;
63
64 fn target_problem(&self) -> &ILP<bool, f64> {
65 &self.target
66 }
67
68 fn extract_solution(
70 &self,
71 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
72 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
73 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
74
75 one_hot_decode_rows(target_solution, self.num_records, self.num_sectors, 0)
76 }
77}
78
79#[reduction(
80 transform = exact {
81 num_vars = "num_records * num_sectors + num_records^2 * num_sectors^2",
82 num_constraints = "num_records + 3 * num_records^2 * num_sectors^2",
83 },
84 unavailable = {
85 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
86 }
87)]
88impl ReduceTo<ILP<bool, f64>> for ExpectedRetrievalCost {
89 type Result = ReductionERCToILP;
90
91 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
92 let num_records = self.num_records();
93 let num_sectors = self.num_sectors();
94 let n = num_records * num_sectors; let num_vars = n + n * n;
96
97 let result = ReductionERCToILP {
98 target: ILP::empty(),
99 num_records,
100 num_sectors,
101 };
102
103 let mut constraints: Vec<LinearConstraint<f64>> = Vec::new();
104
105 for r in 0..num_records {
107 let terms: Vec<(usize, f64)> = (0..num_sectors)
108 .map(|s| (result.x_var(r, s), 1.0))
109 .collect();
110 constraints.push(LinearConstraint::eq(terms, 1.0));
111 }
112
113 for r in 0..num_records {
118 for s in 0..num_sectors {
119 for r2 in 0..num_records {
120 for s2 in 0..num_sectors {
121 let z = result.z_var(r, s, r2, s2);
122 let x1 = result.x_var(r, s);
123 let x2 = result.x_var(r2, s2);
124
125 constraints.extend(mccormick_product(z, x1, x2));
126 }
127 }
128 }
129 }
130
131 let probabilities = self.probabilities();
133 let mut objective: Vec<(usize, f64)> = Vec::new();
134 for r in 0..num_records {
135 for s in 0..num_sectors {
136 for r2 in 0..num_records {
137 for s2 in 0..num_sectors {
138 let lat = latency_distance(num_sectors, s, s2) as f64;
139 if lat > 0.0 {
140 let coeff = lat * probabilities[r] * probabilities[r2];
141 if coeff.abs() > 0.0 {
142 let z = result.z_var(r, s, r2, s2);
143 objective.push((z, coeff));
144 }
145 }
146 }
147 }
148 }
149 }
150
151 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
152 .map_err(Self::target_construction)?;
153
154 Ok(ReductionERCToILP {
155 target,
156 num_records,
157 num_sectors,
158 })
159 }
160}
161
162#[cfg(feature = "example-db")]
163pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
164 use crate::export::SolutionPair;
165
166 vec![crate::example_db::specs::RuleExampleSpec {
167 id: "expectedretrievalcost_to_ilp",
168 build: || {
169 let source = ExpectedRetrievalCost::new(vec![0.5, 0.5], 2).unwrap();
172 let reduction: ReductionERCToILP =
174 ReduceTo::<ILP<bool, f64>>::reduce_to(&source).expect("reduction should succeed");
175 let solver = crate::solvers::ILPSolver::new();
176 let target_config = solver
177 .solve(reduction.target_problem())
178 .expect("canonical example should be feasible");
179 crate::example_db::specs::rule_example_with_witness::<_, ILP<bool, f64>>(
180 source,
181 SolutionPair {
182 source_config: serde_json::json!(vec![0, 1]),
183 target_config: serde_json::to_value(target_config)
184 .expect("solution serialization must succeed"),
185 },
186 )
187 },
188 }]
189}
190
191#[cfg(test)]
192#[path = "../unit_tests/rules/expectedretrievalcost_ilp.rs"]
193mod tests;