1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use bumpalo::Bump;
use bumpalo::collections::CollectIn;
use log::trace;
use rand::rngs::SmallRng;
use rand::seq::IteratorRandom;
use rand::{RngExt, SeedableRng, make_rng};
use thiserror::Error;
use crate::channel_system::{
Action, Channel, ChannelSystem, ChannelSystemRun, CsError, Event, EventType, Location, PgId,
};
use crate::{BooleanExpr, Oracle, RunOutcome, Time, Tracer, Val};
/// Errors produced by a [`TransitionSystem`].
#[derive(Debug, Clone, Copy, Error)]
pub enum TsError {
/// The CS returned an error of its own.
#[error("error from channel system {0:?}")]
ChannelSystem(CsError),
/// The default value set for the port is not the right type,
/// i.e., the type of messages of the channel.
#[error("default port value is not the type of the channel {0:?}")]
WrongPortType(Channel),
}
impl From<CsError> for TsError {
fn from(value: CsError) -> Self {
Self::ChannelSystem(value)
}
}
/// An atomic variable exposed by the [`ChannelSystem to the TransitionSystem`].
#[derive(Debug, Clone, Copy)]
pub enum Atom {
/// A predicate.
State(Channel, usize),
/// A send event.
Event(Channel),
}
/// A definition type that instances new [`CsModelRun`].
#[derive(Debug, Clone)]
pub struct TransitionSystem {
cs: ChannelSystem,
// ports are supposed to be ordered by channel
ports: Vec<Channel>,
vals: Vec<Vec<Val>>,
predicates: Vec<BooleanExpr<Atom>>,
}
impl TransitionSystem {
/// Creates a new [`CsModel`] from a [`ChannelSystemBuilder`].
pub fn new(cs: ChannelSystem) -> Self {
Self {
ports: Vec::new(),
vals: Vec::new(),
cs,
predicates: Vec::new(),
}
}
/// Adds a new port to the [`CsModel`],
/// which is given by an [`Channel`] and a default [`Val`] value.
pub fn add_port(&mut self, channel: Channel, mut value: Vec<Val>) -> Result<(), TsError> {
let types = self.cs.channel(channel)?.0;
if types.len() != value.len() || types.iter().zip(&value).any(|(t, val)| val.r#type() != *t)
{
return Err(TsError::WrongPortType(channel));
}
// Keep ports list ordered
// Don't insert duplicated ports
if let Err(index) = self.ports.binary_search(&channel) {
self.ports.insert(index, channel);
value.shrink_to_fit();
self.vals.insert(index, value);
}
assert!(self.ports.is_sorted());
assert_eq!(self.ports.len(), self.vals.len());
Ok(())
}
/// Adds a new predicate to the [`CsModel`],
/// which is an expression over the CS's channels.
pub fn add_predicate(&mut self, predicate: BooleanExpr<Atom>) -> Result<(), TsError> {
// Make sure predicate type-checks
let _ = predicate.eval::<SmallRng>(
&|port| match port {
Atom::State(channel, idx) => {
let index = self
.ports
.binary_search(&channel)
.expect("port must have been initialized");
self.vals[index][idx]
}
Atom::Event(..) => Val::Boolean(false),
},
None,
);
self.predicates.push(predicate);
Ok(())
}
/// Shrink ports storage to optimize space use.
/// To be called after having added all ports.
pub fn shrink(&mut self) {
self.ports.shrink_to_fit();
self.vals.shrink_to_fit();
}
/// Generates an executable run of the model.
pub fn new_run(&self) -> TransitionSystemRun<'_> {
let mut vals = self.vals.clone();
vals.shrink_to_fit();
let mut pg_list = Vec::from_iter(self.cs.program_graph_ids());
pg_list.shrink_to_fit();
TransitionSystemRun {
cs: self.cs.new_instance(),
ports: &self.ports,
vals,
predicates: &self.predicates,
last_event: None,
pg_list,
rng: make_rng(),
bump: Bump::new(),
}
}
}
/// Transition system model based on a [`ChannelSystem`].
///
/// It is essentially a CS which keeps track of the [`Event`]s produced by the execution
/// and determining a set of predicates.
#[derive(Debug)]
pub struct TransitionSystemRun<'def> {
cs: ChannelSystemRun<'def>,
ports: &'def [Channel],
vals: Vec<Vec<Val>>,
predicates: &'def [BooleanExpr<Atom>],
last_event: Option<(Action, Event)>,
pg_list: Vec<PgId>,
rng: SmallRng,
bump: Bump,
}
impl<'def> Clone for TransitionSystemRun<'def> {
fn clone(&self) -> Self {
Self {
cs: self.cs.clone(),
ports: self.ports,
vals: self.vals.clone(),
predicates: self.predicates,
last_event: self.last_event.clone(),
pg_list: self.pg_list.clone(),
rng: self.rng.clone(),
bump: Bump::new(),
}
}
}
impl<'def> TransitionSystemRun<'def> {
/// Perform a random transition.
///
/// Used to generate Montecarlo-like executions
pub fn transition(&mut self) {
self.last_event = self.montecarlo_transition();
if let Some((_, ref event)) = self.last_event
&& let EventType::Send(ref vals) = event.event_type
&& let Ok(index) = self.ports.binary_search(&event.channel)
{
// Since we have to update old values,
// the vectors are already allocated and their is always the same.
// Copying from slice should be faster than cloning.
self.vals[index].copy_from_slice(vals);
}
}
/// Returns last event processed by model.
#[inline]
pub fn last_event(&self) -> Option<&(Action, Event)> {
self.last_event.as_ref()
}
#[inline]
fn time(&self) -> Time {
self.cs.time()
}
#[inline]
fn time_tick(&mut self) {
self.cs.wait(1).expect("time error")
}
fn labels(&self) -> impl Iterator<Item = bool> {
self.predicates.iter().map(|prop| {
prop.eval::<SmallRng>(
&|port| match port {
Atom::State(channel, idx) => {
let port_idx = self
.ports
.binary_search(&channel)
.expect("port must exist and be initialized");
self.vals[port_idx][idx]
}
Atom::Event(channel) => {
Val::Boolean(self.last_event.as_ref().is_some_and(|(_, e)| {
e.channel == channel && matches!(e.event_type, EventType::Send(..))
}))
}
},
None,
)
})
}
#[inline]
fn state(&self) -> &[Vec<Val>] {
&self.vals
}
/// Runs a single execution of the [`TransitionSystem`] with a given [`Oracle`] and returns a [`RunOutcome`].
pub(crate) fn experiment<O: Oracle>(
&mut self,
mut oracle: O,
running: Arc<AtomicBool>,
) -> RunOutcome {
// reuse vector to avoid allocations
let mut labels = Vec::from_iter(self.labels());
// Initialize oracle with TS initial state
oracle.update_state(&labels);
while oracle.output_guarantees().any(|b| b.is_none()) {
self.transition();
if !running.load(Ordering::Relaxed) {
trace!("run stopped");
return None;
} else if self.last_event().is_some() {
labels.clear();
labels.extend(self.labels());
oracle.update_state(&labels);
} else if self.cs.is_waiting() {
self.time_tick();
oracle.update_time(self.time());
} else {
break;
}
}
trace!("run complete");
let verified = Vec::from_iter(oracle.final_output_guarantees());
Some(verified)
}
/// Runs a single execution of the [`TransitionSystem`] with a given [`Oracle`]
/// and process the execution trace via the given [`Tracer`].
pub(crate) fn trace<T, O: Oracle>(
&mut self,
mut oracle: O,
mut tracer: T,
model_data: &T::ModelData,
) -> RunOutcome
where
T: Tracer,
{
trace!("new run starting");
// reuse vector to avoid allocations
let mut labels = Vec::from_iter(self.labels());
// Initialize oracle with TS initial state
oracle.update_state(&labels);
// WARN FIXME TODO: Initial state is not written as there is no corresponding action/event
// Same issue for time-tick events
while oracle.output_guarantees().any(|b| b.is_none()) {
self.transition();
if let Some((action, event)) = self.last_event() {
tracer.trace(model_data, *action, event, self.time(), self.state());
labels.clear();
labels.extend(self.labels());
oracle.update_state(&labels);
} else if self.cs.is_waiting() {
self.time_tick();
oracle.update_time(self.time());
} else {
break;
}
}
trace!("run complete");
let verified = Vec::from_iter(oracle.final_output_guarantees());
Some(verified)
}
fn montecarlo_transition(&mut self) -> Option<(Action, Event)> {
let mut rand1 = SmallRng::from_rng(&mut self.rng);
// Setting pgs_left as length resets the queue
let mut pgs_left = self.pg_list.len();
while pgs_left > 0 {
// Select random pg within 0..pgs_left
let pg_select = self.rng.random_range(0..pgs_left);
let pg_id = self.pg_list[pg_select];
// Swap selected pg with last element of the queue (possibly itself, probably not worth checking)
// Decrease the length of the queue (so that selected element is removed)
pgs_left -= 1;
self.pg_list.swap(pg_select, pgs_left);
// Execute randomly chosen transitions on the picked PG until an event is generated,
// or no more transition is possible
// NOTE: Special treatment for PGs with single-location state for optimization of this common case.
// Hopefully it will be possible to treat all cases in a general way eventually.
if self
.cs
.program_graph(pg_id)
.expect("pg exists")
.current_states()
.len()
== 1
{
while let Some((action, post_state)) = self
.cs
.nosync_possible_transitions_pg(pg_id)
.expect("pg exists")
.filter_map(|(action, post_states)| {
post_states.choose(&mut rand1).map(|loc| (action, loc))
})
.choose(&mut self.rng)
{
let event = self
.cs
.transition(pg_id, action, &[post_state])
.expect("successful transition");
if event.is_some() {
return event.map(|ev| (action, ev));
}
}
} else {
use bumpalo::collections::Vec as BumpVec;
self.bump.reset();
while let Some((action, post_states)) = self
.cs
.possible_transitions_pg(pg_id)
.expect("pg exists")
.filter_map(|(action, post_states)| {
post_states
.map(|locs| locs.choose(&mut rand1))
.collect_in::<Option<BumpVec<Location>>>(&self.bump)
// .collect::<Option<Vec<Location>>>()
.map(|locs| (action, locs))
})
.choose(&mut self.rng)
{
let event = self
.cs
.transition(pg_id, action, post_states.as_slice())
.expect("successful transition");
if event.is_some() {
return event.map(|ev| (action, ev));
}
}
}
}
None
}
}