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_domain(&self) -> bool {
94 forall|i: int| 0 <= i < self.actuation.allocation.len()
95 && #[trigger] self.actuation.allocation@[i] is Some
96 ==> self.actuation.allocation@[i]->Some_0 > 0
97 }
98
99 pub open spec fn type_invariant(&self) -> bool {
101 &&& self.actuation.invariant()
102 &&& !self.actuation.complete
103 &&& self.support_domain()
104 &&& self.budget.safety_invariant()
105 &&& self.budget.reserved == 0
106 &&& self.budget.pending_eviction == 0
107 &&& self.budget.allocated as int
108 == selected_count(self.actuation.effects@, self.actuation.effects.len() as int)
109 }
110
111 pub open spec fn bounded_sample(&self) -> bool {
113 self.budget.allocated <= self.budget.capacity
114 }
115
116 pub open spec fn support_consistency(&self) -> bool {
118 forall|i: int| 0 <= i < self.actuation.effects.len()
119 && #[trigger] self.actuation.effects@[i] is Some
120 ==> self.actuation.allocation@[i] is Some
121 && self.actuation.allocation@[i]->Some_0 > 0
122 }
123
124 pub open spec fn inv(&self) -> bool {
126 self.type_invariant() && self.bounded_sample() && self.support_consistency()
127 }
128
129 pub fn new(distribution: Vec<u64>, sample_size: usize) -> (sampler: Self)
131 ensures
132 sampler.actuation.num_seats == distribution@.len(),
133 sampler.budget.capacity == sample_size as u64,
134 sampler.budget.allocated == 0,
135 sampler.inv(),
136 forall|i: int| 0 <= i < distribution@.len() ==> {
137 let allocation = #[trigger] sampler.actuation.allocation@[i];
138 if distribution@[i] == 0 {
139 allocation is None
140 } else {
141 allocation == Some(distribution@[i])
142 }
143 },
144 {
145 let length = distribution.len();
146 let mut allocation: Vec<Option<u64>> = Vec::new();
147 let mut index: usize = 0;
148 while index < length
149 invariant
150 index <= length,
151 length == distribution@.len(),
152 allocation.len() == index,
153 forall|i: int| 0 <= i < index ==> {
154 let value = #[trigger] allocation@[i];
155 if distribution@[i] == 0 {
156 value is None
157 } else {
158 value == Some(distribution@[i])
159 }
160 },
161 decreases length - index,
162 {
163 let weight = distribution[index];
164 if weight == 0 {
165 allocation.push(None);
166 } else {
167 allocation.push(Some(weight));
168 }
169 index += 1;
170 }
171 let actuation = ActuationPass::new(allocation, length);
172 let budget = Budget::new(sample_size as u64);
173 proof { selected_count_none(actuation.effects@, actuation.effects.len() as int); }
174 Self { actuation, budget }
175 }
176
177 pub fn weight(&self, item: usize) -> (weight: u64)
179 requires self.inv(), item < self.actuation.num_seats,
180 ensures
181 weight == if self.actuation.allocation@[item as int] is Some {
182 self.actuation.allocation@[item as int]->Some_0
183 } else { 0 },
184 {
185 self.actuation.allocation[item].unwrap_or(0)
186 }
187
188 pub fn contains_exec(&self, item: usize) -> (selected: bool)
190 requires self.inv(),
191 ensures selected == self.contains(item),
192 {
193 if item >= self.actuation.effects.len() {
194 false
195 } else {
196 self.actuation.is_actuated(item)
197 }
198 }
199
200 pub fn sample(&mut self, item: usize)
202 requires
203 old(self).inv(),
204 item < old(self).actuation.num_seats,
205 old(self).budget.allocated < old(self).budget.capacity,
206 old(self).actuation.allocation@[item as int] is Some,
207 old(self).actuation.effects@[item as int] is None,
208 ensures
209 final(self).inv(),
210 final(self).budget.capacity == old(self).budget.capacity,
211 final(self).budget.allocated == old(self).budget.allocated + 1,
212 final(self).actuation.allocation@ == old(self).actuation.allocation@,
213 final(self).actuation.effects@
214 == old(self).actuation.effects@.update(
215 item as int,
216 old(self).actuation.allocation@[item as int],
217 ),
218 {
219 let ghost prior_effects = self.actuation.effects@;
220 let _accepted = self.budget.try_allocate(1);
221 assert(_accepted);
222 self.actuation.actuate(item);
223 proof {
224 selected_count_update(
225 prior_effects,
226 item as int,
227 self.actuation.effects@[item as int],
228 prior_effects.len() as int,
229 );
230 }
231 }
232
233 pub fn zero(&mut self, item: usize) -> (accepted: bool)
235 requires old(self).inv(),
236 ensures
237 final(self).inv(),
238 accepted == (item < old(self).actuation.num_seats && !old(self).contains(item)),
239 final(self).budget == old(self).budget,
240 final(self).actuation.effects@ == old(self).actuation.effects@,
241 accepted && old(self).actuation.allocation@[item as int] is Some
242 ==> final(self).actuation.allocation@
243 == old(self).actuation.allocation@.update(item as int, None),
244 !accepted ==> final(self).actuation.allocation@ == old(self).actuation.allocation@,
245 {
246 if item >= self.actuation.num_seats || self.contains_exec(item) {
247 return false;
248 }
249 if self.actuation.is_allocated(item) {
250 self.actuation.deallocate(item);
251 }
252 true
253 }
254
255 pub fn draw_weighted(&mut self, item: usize, entropy: u64) -> (accepted: bool)
257 requires old(self).inv(), item < old(self).actuation.num_seats,
258 ensures final(self).inv(),
259 {
260 if self.budget.allocated >= self.budget.capacity {
261 return false;
262 }
263 let weight = self.weight(item);
264 if entropy >= weight || self.contains_exec(item) {
265 return false;
266 }
267 self.sample(item);
268 true
269 }
270
271 pub fn draw_uniform(&mut self, item: usize) -> (accepted: bool)
273 requires old(self).inv(), item < old(self).actuation.num_seats,
274 ensures final(self).inv(),
275 {
276 if self.budget.allocated >= self.budget.capacity {
277 return false;
278 }
279 let weight = self.weight(item);
280 if weight == 0 || self.contains_exec(item) {
281 return false;
282 }
283 self.sample(item);
284 true
285 }
286}
287
288}