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