problemreductions/models/misc/
expected_retrieval_cost.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry, ProblemSizeFieldEntry};
7use crate::traits::Problem;
8use crate::types::Min;
9use serde::{Deserialize, Serialize};
10
11const FLOAT_TOLERANCE: f64 = 1e-9;
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "ExpectedRetrievalCost",
16 display_name: "Expected Retrieval Cost",
17 aliases: &[],
18 dimensions: &[],
19 module_path: module_path!(),
20 description: "Assign records to circular storage sectors to minimize expected retrieval latency",
21 fields: &[
22 FieldInfo { name: "probabilities", type_name: "Vec<f64>", description: "Access probabilities p(r) for each record" },
23 FieldInfo { name: "num_sectors", type_name: "usize", description: "Number of sectors on the drum-like device" },
24 ],
25 }
26}
27
28inventory::submit! {
29 ProblemSizeFieldEntry {
30 name: "ExpectedRetrievalCost",
31 fields: &["num_records", "num_sectors"],
32 }
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ExpectedRetrievalCost {
37 probabilities: Vec<f64>,
38 num_sectors: usize,
39}
40
41impl ExpectedRetrievalCost {
42 pub fn new(probabilities: Vec<f64>, num_sectors: usize) -> Self {
43 assert!(
44 !probabilities.is_empty(),
45 "ExpectedRetrievalCost requires at least one record"
46 );
47 assert!(
48 num_sectors >= 2,
49 "ExpectedRetrievalCost requires at least two sectors"
50 );
51 for &probability in &probabilities {
52 assert!(
53 probability.is_finite(),
54 "probabilities must be finite real numbers"
55 );
56 assert!(
57 (0.0..=1.0).contains(&probability),
58 "probabilities must lie in [0, 1]"
59 );
60 }
61 let total_probability: f64 = probabilities.iter().sum();
62 assert!(
63 (total_probability - 1.0).abs() <= FLOAT_TOLERANCE,
64 "probabilities must sum to 1.0"
65 );
66 Self {
67 probabilities,
68 num_sectors,
69 }
70 }
71
72 pub fn probabilities(&self) -> &[f64] {
73 &self.probabilities
74 }
75
76 pub fn num_records(&self) -> usize {
77 self.probabilities.len()
78 }
79
80 pub fn num_sectors(&self) -> usize {
81 self.num_sectors
82 }
83
84 pub fn sector_masses(&self, config: &[usize]) -> Option<Vec<f64>> {
85 if config.len() != self.num_records() {
86 return None;
87 }
88
89 let mut masses = vec![0.0; self.num_sectors];
90 for (record, §or) in config.iter().enumerate() {
91 if sector >= self.num_sectors {
92 return None;
93 }
94 masses[sector] += self.probabilities[record];
95 }
96 Some(masses)
97 }
98
99 pub fn expected_cost(&self, config: &[usize]) -> Option<f64> {
100 let masses = self.sector_masses(config)?;
101 let mut total = 0.0;
102 for source in 0..self.num_sectors {
103 for target in 0..self.num_sectors {
104 total += masses[source]
105 * masses[target]
106 * latency_distance(self.num_sectors, source, target) as f64;
107 }
108 }
109 Some(total)
110 }
111
112 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
113 self.expected_cost(config).is_some()
114 }
115}
116
117impl Problem for ExpectedRetrievalCost {
118 const NAME: &'static str = "ExpectedRetrievalCost";
119 type Value = Min<f64>;
120
121 fn variant() -> Vec<(&'static str, &'static str)> {
122 crate::variant_params![]
123 }
124
125 fn dims(&self) -> Vec<usize> {
126 vec![self.num_sectors; self.num_records()]
127 }
128
129 fn evaluate(&self, config: &[usize]) -> Min<f64> {
130 match self.expected_cost(config) {
131 Some(cost) => Min(Some(cost)),
132 None => Min(None),
133 }
134 }
135}
136
137fn latency_distance(num_sectors: usize, source: usize, target: usize) -> usize {
138 if source < target {
139 target - source - 1
140 } else {
141 num_sectors - source + target - 1
142 }
143}
144
145crate::declare_variants! {
146 default ExpectedRetrievalCost => "num_sectors ^ num_records",
147}
148
149#[cfg(feature = "example-db")]
150pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
151 vec![crate::example_db::specs::ModelExampleSpec {
152 id: "expected_retrieval_cost",
153 instance: Box::new(ExpectedRetrievalCost::new(
154 vec![0.2, 0.15, 0.15, 0.2, 0.1, 0.2],
155 3,
156 )),
157 optimal_config: vec![0, 1, 2, 1, 0, 2],
158 optimal_value: serde_json::json!(1.0025),
159 }]
160}
161
162#[cfg(test)]
163#[path = "../../unit_tests/models/misc/expected_retrieval_cost.rs"]
164mod tests;