automation_structures/compositions/
sampler.rs1use crate::primitives::actuation_pass::ActuationPass;
9use crate::primitives::budget::Budget;
10use vstd::prelude::*;
11
12verus! {
13
14pub open spec fn present<T>(value: Option<T>) -> int {
16 if value is Some { 1 } else { 0 }
17}
18
19pub open spec fn selected_count<T>(effects: Seq<Option<T>>, n: int) -> int
21 decreases n,
22{
23 if n <= 0 || n > effects.len() {
24 0
25 } else {
26 present(effects[n - 1]) + selected_count(effects, n - 1)
27 }
28}
29
30proof fn selected_count_none<T>(effects: Seq<Option<T>>, n: int)
31 requires
32 0 <= n <= effects.len(),
33 forall|i: int| 0 <= i < effects.len() ==> #[trigger] effects[i] is None,
34 ensures selected_count(effects, n) == 0,
35 decreases n,
36{
37 if n > 0 {
38 selected_count_none(effects, n - 1);
39 }
40}
41
42proof fn selected_count_update_unaffected<T>(
43 effects: Seq<Option<T>>,
44 index: int,
45 replacement: Option<T>,
46 n: int,
47)
48 requires 0 <= n <= index < effects.len(),
49 ensures selected_count(effects.update(index, replacement), n)
50 == selected_count(effects, n),
51 decreases n,
52{
53 if n > 0 {
54 selected_count_update_unaffected(effects, index, replacement, n - 1);
55 assert(effects.update(index, replacement)[n - 1] == effects[n - 1]);
56 }
57}
58
59proof fn selected_count_update<T>(
60 effects: Seq<Option<T>>,
61 index: int,
62 replacement: Option<T>,
63 n: int,
64)
65 requires 0 <= index < n <= effects.len(),
66 ensures selected_count(effects.update(index, replacement), n)
67 == selected_count(effects, n) - present(effects[index]) + present(replacement),
68 decreases n,
69{
70 if n == index + 1 {
71 selected_count_update_unaffected(effects, index, replacement, index);
72 assert(effects.update(index, replacement)[index] == replacement);
73 } else {
74 selected_count_update(effects, index, replacement, n - 1);
75 assert(effects.update(index, replacement)[n - 1] == effects[n - 1]);
76 }
77}
78
79pub struct Sampler {
81 pub actuation: ActuationPass,
83 pub budget: Budget,
85}
86
87impl Sampler {
88 pub open spec fn contains(&self, item: usize) -> bool {
90 item < self.actuation.effects.len() && self.actuation.effects@[item as int] is Some
91 }
92
93 pub open spec fn support_weight(&self, item: usize) -> u64 {
95 if item < self.actuation.allocation.len()
96 && self.actuation.allocation@[item as int] is Some
97 {
98 self.actuation.allocation@[item as int]->Some_0
99 } else {
100 0
101 }
102 }
103
104 pub open spec fn weighted_draw_enabled(&self, item: usize, entropy: u64) -> bool {
106 &&& item < self.actuation.num_seats
107 &&& self.budget.allocated < self.budget.capacity
108 &&& entropy < self.support_weight(item)
109 &&& !self.contains(item)
110 }
111
112 pub open spec fn uniform_draw_enabled(&self, item: usize) -> bool {
114 &&& item < self.actuation.num_seats
115 &&& self.budget.allocated < self.budget.capacity
116 &&& self.support_weight(item) > 0
117 &&& !self.contains(item)
118 }
119
120 pub open spec fn support_domain(&self) -> bool {
122 forall|i: int| 0 <= i < self.actuation.allocation.len()
123 && #[trigger] self.actuation.allocation@[i] is Some
124 ==> self.actuation.allocation@[i]->Some_0 > 0
125 }
126
127 pub open spec fn type_invariant(&self) -> bool {
129 &&& self.actuation.invariant()
130 &&& !self.actuation.complete
131 &&& self.support_domain()
132 &&& self.budget.safety_invariant()
133 &&& self.budget.reserved == 0
134 &&& self.budget.pending_eviction == 0
135 &&& self.budget.allocated as int
136 == selected_count(self.actuation.effects@, self.actuation.effects.len() as int)
137 }
138
139 pub open spec fn bounded_sample(&self) -> bool {
141 self.budget.allocated <= self.budget.capacity
142 }
143
144 pub open spec fn support_consistency(&self) -> bool {
146 forall|i: int| 0 <= i < self.actuation.effects.len()
147 && #[trigger] self.actuation.effects@[i] is Some
148 ==> self.actuation.allocation@[i] is Some
149 && self.actuation.allocation@[i]->Some_0 > 0
150 }
151
152 pub open spec fn inv(&self) -> bool {
154 self.type_invariant() && self.bounded_sample() && self.support_consistency()
155 }
156
157 pub fn new(distribution: Vec<u64>, sample_size: usize) -> (sampler: Self)
159 ensures
160 sampler.actuation.num_seats == distribution@.len(),
161 sampler.budget.capacity == sample_size as u64,
162 sampler.budget.allocated == 0,
163 sampler.budget.reserved == 0,
164 sampler.budget.pending_eviction == 0,
165 !sampler.actuation.complete,
166 forall|i: int| 0 <= i < distribution@.len() ==>
167 #[trigger] sampler.actuation.effects@[i] is None,
168 sampler.inv(),
169 forall|i: int| 0 <= i < distribution@.len() ==> {
170 let allocation = #[trigger] sampler.actuation.allocation@[i];
171 if distribution@[i] == 0 {
172 allocation is None
173 } else {
174 allocation == Some(distribution@[i])
175 }
176 },
177 {
178 let length = distribution.len();
179 let mut allocation: Vec<Option<u64>> = Vec::new();
180 let mut index: usize = 0;
181 while index < length
182 invariant
183 index <= length,
184 length == distribution@.len(),
185 allocation.len() == index,
186 forall|i: int| 0 <= i < index ==> {
187 let value = #[trigger] allocation@[i];
188 if distribution@[i] == 0 {
189 value is None
190 } else {
191 value == Some(distribution@[i])
192 }
193 },
194 decreases length - index,
195 {
196 let weight = distribution[index];
197 if weight == 0 {
198 allocation.push(None);
199 } else {
200 allocation.push(Some(weight));
201 }
202 index += 1;
203 }
204 let actuation = ActuationPass::new(allocation, length);
205 let budget = Budget::new(sample_size as u64);
206 proof { selected_count_none(actuation.effects@, actuation.effects.len() as int); }
207 Self { actuation, budget }
208 }
209
210 pub fn weight(&self, item: usize) -> (weight: u64)
212 requires self.inv(), item < self.actuation.num_seats,
213 ensures weight == self.support_weight(item),
214 {
215 self.actuation.allocation[item].unwrap_or(0)
216 }
217
218 pub fn contains_exec(&self, item: usize) -> (selected: bool)
220 requires self.inv(),
221 ensures selected == self.contains(item),
222 {
223 if item >= self.actuation.effects.len() {
224 false
225 } else {
226 self.actuation.is_actuated(item)
227 }
228 }
229
230 pub fn sample(&mut self, item: usize)
232 requires
233 old(self).inv(),
234 item < old(self).actuation.num_seats,
235 old(self).budget.allocated < old(self).budget.capacity,
236 old(self).actuation.allocation@[item as int] is Some,
237 old(self).actuation.effects@[item as int] is None,
238 ensures
239 final(self).inv(),
240 final(self).budget.capacity == old(self).budget.capacity,
241 final(self).budget.allocated == old(self).budget.allocated + 1,
242 final(self).actuation.allocation@ == old(self).actuation.allocation@,
243 final(self).actuation.effects@
244 == old(self).actuation.effects@.update(
245 item as int,
246 old(self).actuation.allocation@[item as int],
247 ),
248 {
249 let ghost prior_effects = self.actuation.effects@;
250 let _accepted = self.budget.try_allocate(1);
251 assert(_accepted);
252 self.actuation.actuate(item);
253 proof {
254 selected_count_update(
255 prior_effects,
256 item as int,
257 self.actuation.effects@[item as int],
258 prior_effects.len() as int,
259 );
260 }
261 }
262
263 pub fn zero(&mut self, item: usize) -> (accepted: bool)
265 requires old(self).inv(),
266 ensures
267 final(self).inv(),
268 accepted == (item < old(self).actuation.num_seats && !old(self).contains(item)),
269 final(self).budget == old(self).budget,
270 final(self).actuation.num_seats == old(self).actuation.num_seats,
271 final(self).actuation.complete == old(self).actuation.complete,
272 final(self).actuation.effects@ == old(self).actuation.effects@,
273 accepted ==> final(self).actuation.allocation@
274 == if old(self).actuation.allocation@[item as int] is Some {
275 old(self).actuation.allocation@.update(item as int, None)
276 } else {
277 old(self).actuation.allocation@
278 },
279 !accepted ==> final(self).actuation.allocation@ == old(self).actuation.allocation@,
280 {
281 if item >= self.actuation.num_seats || self.contains_exec(item) {
282 return false;
283 }
284 if self.actuation.is_allocated(item) {
285 self.actuation.deallocate(item);
286 }
287 true
288 }
289
290 pub fn draw_weighted(&mut self, item: usize, entropy: u64) -> (accepted: bool)
292 requires old(self).inv(), item < old(self).actuation.num_seats,
293 ensures
294 final(self).inv(),
295 accepted == old(self).weighted_draw_enabled(item, entropy),
296 final(self).budget.capacity == old(self).budget.capacity,
297 final(self).budget.reserved == old(self).budget.reserved,
298 final(self).budget.pending_eviction == old(self).budget.pending_eviction,
299 final(self).actuation.num_seats == old(self).actuation.num_seats,
300 final(self).actuation.allocation@ == old(self).actuation.allocation@,
301 final(self).actuation.complete == old(self).actuation.complete,
302 accepted ==> {
303 &&& final(self).budget.allocated == old(self).budget.allocated + 1
304 &&& final(self).actuation.effects@
305 == old(self).actuation.effects@.update(
306 item as int,
307 old(self).actuation.allocation@[item as int],
308 )
309 },
310 !accepted ==> {
311 &&& final(self).budget.allocated == old(self).budget.allocated
312 &&& final(self).actuation.effects@ == old(self).actuation.effects@
313 },
314 {
315 if self.budget.allocated >= self.budget.capacity {
316 return false;
317 }
318 let weight = self.weight(item);
319 if entropy >= weight || self.contains_exec(item) {
320 return false;
321 }
322 self.sample(item);
323 true
324 }
325
326 pub fn draw_uniform(&mut self, item: usize) -> (accepted: bool)
328 requires old(self).inv(), item < old(self).actuation.num_seats,
329 ensures
330 final(self).inv(),
331 accepted == old(self).uniform_draw_enabled(item),
332 final(self).budget.capacity == old(self).budget.capacity,
333 final(self).budget.reserved == old(self).budget.reserved,
334 final(self).budget.pending_eviction == old(self).budget.pending_eviction,
335 final(self).actuation.num_seats == old(self).actuation.num_seats,
336 final(self).actuation.allocation@ == old(self).actuation.allocation@,
337 final(self).actuation.complete == old(self).actuation.complete,
338 accepted ==> {
339 &&& final(self).budget.allocated == old(self).budget.allocated + 1
340 &&& final(self).actuation.effects@
341 == old(self).actuation.effects@.update(
342 item as int,
343 old(self).actuation.allocation@[item as int],
344 )
345 },
346 !accepted ==> {
347 &&& final(self).budget.allocated == old(self).budget.allocated
348 &&& final(self).actuation.effects@ == old(self).actuation.effects@
349 },
350 {
351 if self.budget.allocated >= self.budget.capacity {
352 return false;
353 }
354 let weight = self.weight(item);
355 if weight == 0 || self.contains_exec(item) {
356 return false;
357 }
358 self.sample(item);
359 true
360 }
361}
362
363}