scan_core/program_graph/builder.rs
1use std::{collections::BTreeMap, ops::RangeBounds};
2
3use super::*;
4use crate::grammar::{Type, Val};
5use log::info;
6
7type LocationBuilderData = (BTreeMap<Action, Vec<Transition>>, Vec<(Clock, TimeRange)>);
8
9/// Defines and builds a PG.
10#[derive(Debug, Clone)]
11pub struct ProgramGraphBuilder {
12 // initial_states: Vec<Location>,
13 initial_states: Vec<Location>,
14 // Effects are indexed by actions
15 effects: Vec<Effect>,
16 // Transitions are indexed by locations
17 // Time invariants of each location
18 locations: Vec<LocationBuilderData>,
19 // Local variables with initial value.
20 vars: Vec<Val>,
21 // Number of clocks
22 clocks: u16,
23}
24
25impl Default for ProgramGraphBuilder {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl ProgramGraphBuilder {
32 /// Creates a new [`ProgramGraphBuilder`].
33 /// At creation, this will only have the initial location with no variables, no actions and no transitions.
34 pub fn new() -> Self {
35 Self {
36 initial_states: Vec::new(),
37 effects: Vec::new(),
38 vars: Vec::new(),
39 locations: Vec::new(),
40 clocks: 0,
41 }
42 }
43
44 // Gets the type of a variable.
45 pub(crate) fn var_type(&self, var: Var) -> Result<Type, PgError> {
46 self.vars
47 .get(var.0 as usize)
48 .map(|val| Val::r#type(*val))
49 .ok_or(PgError::MissingVar(var))
50 }
51
52 /// Adds a new variable with the given initial value (and the inferred type) to the PG.
53 ///
54 /// ```
55 /// # use scan_core::program_graph::{PgExpression, ProgramGraphBuilder};
56 /// # let mut pg_builder = ProgramGraphBuilder::new();
57 /// // Create a new action
58 /// let action = pg_builder.new_action();
59 ///
60 /// // Create a value to assign the expression.
61 /// let val = (PgExpression::from(40i64) + PgExpression::from(40i64)).unwrap().eval_constant().unwrap();
62 ///
63 /// // Create a new variable
64 /// let var = pg_builder
65 /// .new_var(val);
66 /// ```
67 pub fn new_var(&mut self, val: Val) -> Var {
68 let idx = self.vars.len();
69 self.vars.push(val);
70 Var(idx as u16)
71 }
72
73 /// Adds a new clock and returns a [`Clock`] id object.
74 ///
75 /// See also [`crate::channel_system::ChannelSystemBuilder::new_clock`].
76 pub fn new_clock(&mut self) -> Clock {
77 // We adopt the convention of indexing n clocks from 0 to n-1
78 let idx = self.clocks;
79 self.clocks += 1;
80 Clock(idx)
81 }
82
83 /// Adds a new action to the PG.
84 ///
85 /// ```
86 /// # use scan_core::program_graph::{Action, ProgramGraphBuilder};
87 /// # let mut pg_builder = ProgramGraphBuilder::new();
88 /// // Create a new action
89 /// let action: Action = pg_builder.new_action();
90 /// ```
91 #[inline(always)]
92 pub fn new_action(&mut self) -> Action {
93 let idx = self.effects.len();
94 self.effects.push(Effect::Effects(Vec::new(), Vec::new()));
95 Action(idx as ActionIdx)
96 }
97
98 /// Associates a clock reset to an action.
99 ///
100 /// Returns an error if the clock to be reset does not belong to the Program Graph.
101 ///
102 /// ```
103 /// # use scan_core::program_graph::{Clock, ProgramGraphBuilder};
104 /// # let mut pg_builder = ProgramGraphBuilder::new();
105 /// # let mut other_pg_builder = ProgramGraphBuilder::new();
106 /// let action = pg_builder.new_action();
107 /// let clock = other_pg_builder.new_clock();
108 /// // Associate action with clock reset
109 /// pg_builder
110 /// .add_reset(action, clock)
111 /// .expect_err("the clock does not belong to this PG");
112 /// ```
113 pub fn add_reset(&mut self, action: Action, clock: Clock) -> Result<(), PgError> {
114 if action == EPSILON {
115 return Err(PgError::NoEffects);
116 }
117 if clock.0 >= self.clocks {
118 return Err(PgError::MissingClock(clock));
119 }
120 match self
121 .effects
122 .get_mut(action.0 as usize)
123 .ok_or(PgError::MissingAction(action))?
124 {
125 Effect::Effects(_, resets) => {
126 resets.push(clock);
127 Ok(())
128 }
129 Effect::Send(_) => Err(PgError::EffectOnSend),
130 Effect::Receive(_) => Err(PgError::EffectOnReceive),
131 }
132 }
133
134 /// Adds an effect to the given action.
135 /// Requires specifying which variable is assigned the value of which expression whenever the action triggers a transition.
136 ///
137 /// It fails if the type of the variable and that of the expression do not match.
138 ///
139 /// ```
140 /// # use scan_core::program_graph::{Action, PgExpression, ProgramGraphBuilder, Var};
141 /// # use scan_core::Val;
142 /// # let mut pg_builder = ProgramGraphBuilder::new();
143 /// // Create a new action
144 /// let action: Action = pg_builder.new_action();
145 ///
146 /// // Create a new variable
147 /// let var: Var = pg_builder.new_var(Val::from(true)).expect("expression is well-typed");
148 ///
149 /// // Add an effect to the action
150 /// pg_builder
151 /// .add_effect(action, var, PgExpression::from(1i64))
152 /// .expect_err("var is of type bool but expression is of type integer");
153 /// pg_builder
154 /// .add_effect(action, var, PgExpression::from(false))
155 /// .expect("var and expression type match");
156 /// ```
157 pub fn add_effect(
158 &mut self,
159 action: Action,
160 var: Var,
161 effect: PgExpression,
162 ) -> Result<(), PgError> {
163 if action == EPSILON {
164 return Err(PgError::NoEffects);
165 }
166 effect
167 .context(&|var| self.vars.get(var.0 as usize).map(|val| Val::r#type(*val)))
168 .map_err(PgError::Type)?;
169 let var_type = self
170 .vars
171 .get(var.0 as usize)
172 .map(|val| Val::r#type(*val))
173 .ok_or_else(|| PgError::MissingVar(var.to_owned()))?;
174 if var_type == effect.r#type() {
175 match self
176 .effects
177 .get_mut(action.0 as usize)
178 .ok_or(PgError::MissingAction(action))?
179 {
180 Effect::Effects(effects, _) => {
181 effects.push((var, effect));
182 Ok(())
183 }
184 Effect::Send(_) => Err(PgError::EffectOnSend),
185 Effect::Receive(_) => Err(PgError::EffectOnReceive),
186 }
187 } else {
188 Err(PgError::TypeMismatch)
189 }
190 }
191
192 pub(crate) fn new_send(&mut self, mut msgs: Vec<PgExpression>) -> Result<Action, PgError> {
193 // Check message is well-typed
194 msgs.iter()
195 .try_for_each(|msg| {
196 msg.context(&|var| self.vars.get(var.0 as usize).map(|val| Val::r#type(*val)))
197 })
198 .map_err(PgError::Type)?;
199 // Actions are indexed progressively
200 let idx = self.effects.len();
201 msgs.shrink_to_fit();
202 self.effects.push(Effect::Send(msgs));
203 Ok(Action(idx as ActionIdx))
204 }
205
206 pub(crate) fn new_receive(&mut self, mut vars: Vec<Var>) -> Result<Action, PgError> {
207 if let Some(var) = vars.iter().find(|var| self.vars.len() as u16 <= var.0) {
208 Err(PgError::MissingVar(var.to_owned()))
209 } else {
210 // Actions are indexed progressively
211 let idx = self.effects.len();
212 vars.shrink_to_fit();
213 self.effects.push(Effect::Receive(vars));
214 Ok(Action(idx as ActionIdx))
215 }
216 }
217
218 /// Adds a new location to the PG and returns its [`Location`] indexing object.
219 #[inline(always)]
220 pub fn new_location(&mut self) -> Location {
221 self.new_timed_location(Vec::new())
222 .expect("new untimed location")
223 }
224
225 /// Adds a new location to the PG with the given time invariants,
226 /// and returns its [`Location`] indexing object.
227 pub fn new_timed_location(
228 &mut self,
229 mut invariants: Vec<(Clock, TimeRange)>,
230 ) -> Result<Location, PgError> {
231 if let Some((clock, _)) = invariants.iter().find(|(c, _)| c.0 >= self.clocks) {
232 Err(PgError::MissingClock(*clock))
233 } else {
234 // Locations are indexed progressively
235 let idx = self.locations.len();
236 invariants.sort_unstable_by_key(|(c, _)| *c);
237 invariants.shrink_to_fit();
238 self.locations.push((BTreeMap::new(), invariants));
239 Ok(Location(idx as LocationIdx))
240 }
241 }
242
243 /// Adds a new (synchronous) process to the PG starting from the given [`Location`].
244 pub fn new_process(&mut self, location: Location) -> Result<(), PgError> {
245 self.locations
246 .get(location.0 as usize)
247 .ok_or(PgError::MissingLocation(location))?
248 .1 // location's time invariants
249 .iter()
250 .all(|(_, range)| {
251 // All clocks start at time 0
252 range.contains(&0)
253 })
254 .then(|| self.initial_states.push(location))
255 .ok_or(PgError::Invariant)
256 }
257
258 /// Adds a new process starting at a new location to the PG and returns the [`Location`] indexing object.
259 #[inline(always)]
260 pub fn new_initial_location(&mut self) -> Location {
261 self.new_initial_timed_location(Vec::new())
262 .expect("new untimed location")
263 }
264
265 /// Adds a new process starting at a new location to the PG with the given time invariants,
266 /// and returns the [`Location`] indexing object.
267 pub fn new_initial_timed_location(
268 &mut self,
269 invariants: Vec<(Clock, TimeRange)>,
270 ) -> Result<Location, PgError> {
271 let location = self.new_timed_location(invariants)?;
272 self.new_process(location)?;
273 Ok(location)
274 }
275
276 /// Adds a transition to the PG.
277 /// Requires specifying:
278 ///
279 /// - state pre-transition,
280 /// - action triggering the transition,
281 /// - state post-transition, and
282 /// - (optionally) boolean expression guarding the transition.
283 ///
284 /// Fails if the provided guard is not a boolean expression.
285 ///
286 /// ```
287 /// # use scan_core::program_graph::ProgramGraphBuilder;
288 /// # use scan_core::BooleanExpr;
289 /// # let mut pg_builder = ProgramGraphBuilder::new();
290 /// // The builder is initialized with an initial location
291 /// let initial_loc = pg_builder.new_initial_location();
292 ///
293 /// // Create a new action
294 /// let action = pg_builder.new_action();
295 ///
296 /// // Add a transition
297 /// pg_builder
298 /// .add_transition(initial_loc, action, initial_loc, None)
299 /// .expect("this transition can be added");
300 /// pg_builder
301 /// .add_transition(initial_loc, action, initial_loc, Some(BooleanExpr::from(false)))
302 /// .expect("this one too");
303 /// ```
304 #[inline(always)]
305 pub fn add_transition(
306 &mut self,
307 pre: Location,
308 action: Action,
309 post: Location,
310 guard: Option<PgGuard>,
311 ) -> Result<(), PgError> {
312 self.add_timed_transition(pre, action, post, guard, Vec::new())
313 }
314
315 /// Adds a timed transition to the PG under timed constraints.
316 /// Requires specifying the same data as [`ProgramGraphBuilder::add_transition`],
317 /// plus a slice of time constraints.
318 ///
319 /// Fails if the provided guard is not a boolean expression.
320 ///
321 /// ```
322 /// # use scan_core::program_graph::ProgramGraphBuilder;
323 /// # use scan_core::BooleanExpr;
324 /// # let mut pg_builder = ProgramGraphBuilder::new();
325 /// // The builder is initialized with an initial location
326 /// let initial_loc = pg_builder.new_initial_location();
327 ///
328 /// // Create a new action
329 /// let action = pg_builder.new_action();
330 ///
331 /// // Add a new clock
332 /// let clock = pg_builder.new_clock();
333 ///
334 /// // Add a timed transition
335 /// pg_builder
336 /// .add_timed_transition(initial_loc, action, initial_loc, None, vec![(clock, None, Some(1))])
337 /// .expect("this transition can be added");
338 /// pg_builder
339 /// .add_timed_transition(initial_loc, action, initial_loc, Some(BooleanExpr::from(false)), vec![(clock, Some(1), None)])
340 /// .expect("this one too");
341 /// ```
342 pub fn add_timed_transition(
343 &mut self,
344 pre: Location,
345 action: Action,
346 post: Location,
347 guard: Option<PgGuard>,
348 mut constraints: Vec<(Clock, TimeRange)>,
349 ) -> Result<(), PgError> {
350 // Check 'pre' and 'post' locations exists
351 if self.locations.len() as LocationIdx <= pre.0 {
352 Err(PgError::MissingLocation(pre))
353 } else if self.locations.len() as LocationIdx <= post.0 {
354 Err(PgError::MissingLocation(post))
355 } else if action != EPSILON && self.effects.len() as ActionIdx <= action.0 {
356 // Check 'action' exists
357 Err(PgError::MissingAction(action))
358 } else if let Some((clock, _)) = constraints.iter().find(|(c, _)| c.0 >= self.clocks) {
359 Err(PgError::MissingClock(*clock))
360 } else {
361 if let Some(ref guard) = guard {
362 guard
363 .context(&|var| self.vars.get(var.0 as usize).map(|val| Val::r#type(*val)))
364 .map_err(PgError::Type)?;
365 }
366 let (transitions, _) = &mut self.locations[pre.0 as usize];
367 constraints.sort_unstable_by_key(|(c, _)| *c);
368 constraints.shrink_to_fit();
369 let transition = (post, guard, constraints);
370 // WARN: Actions have to be inserted in order but insertion has worst-case complexity O(n)
371 // so in some cases insertion of all actions could have complexity O(n^2).
372 // In practice though this is unlikely to ever be a bottleneck
373 match transitions.get_mut(&action) {
374 Some(action_transitions) => action_transitions.push(transition),
375 None => {
376 let _ = transitions.insert(action, vec![transition]);
377 }
378 }
379 Ok(())
380 }
381 }
382
383 /// Adds an autonomous transition to the PG, i.e., a transition enabled by the epsilon action.
384 /// Requires specifying:
385 ///
386 /// - state pre-transition,
387 /// - state post-transition, and
388 /// - (optionally) boolean expression guarding the transition.
389 ///
390 /// Fails if the provided guard is not a boolean expression.
391 ///
392 /// ```
393 /// # use scan_core::program_graph::ProgramGraphBuilder;
394 /// # use scan_core::BooleanExpr;
395 /// # let mut pg_builder = ProgramGraphBuilder::new();
396 /// // The builder is initialized with an initial location
397 /// let initial_loc = pg_builder.new_initial_location();
398 ///
399 /// // Add a transition
400 /// pg_builder
401 /// .add_autonomous_transition(initial_loc, initial_loc, None)
402 /// .expect("this autonomous transition can be added");
403 /// pg_builder
404 /// .add_autonomous_transition(initial_loc, initial_loc, Some(BooleanExpr::from(false)))
405 /// .expect("this one too");
406 /// ```
407 #[inline(always)]
408 pub fn add_autonomous_transition(
409 &mut self,
410 pre: Location,
411 post: Location,
412 guard: Option<PgGuard>,
413 ) -> Result<(), PgError> {
414 self.add_transition(pre, EPSILON, post, guard)
415 }
416
417 /// Adds an autonomous timed transition to the PG, i.e., a transition enabled by the epsilon action under time constraints.
418 /// Requires specifying the same data as [`ProgramGraphBuilder::add_autonomous_transition`],
419 /// plus a slice of time constraints.
420 ///
421 /// Fails if the provided guard is not a boolean expression.
422 ///
423 /// ```
424 /// # use scan_core::program_graph::ProgramGraphBuilder;
425 /// # use scan_core::BooleanExpr;
426 /// # let mut pg_builder = ProgramGraphBuilder::new();
427 /// // The builder is initialized with an initial location
428 /// let initial_loc = pg_builder.new_initial_location();
429 ///
430 /// // Add a new clock
431 /// let clock = pg_builder.new_clock();
432 ///
433 /// // Add an autonomous timed transition
434 /// pg_builder
435 /// .add_autonomous_timed_transition(initial_loc, initial_loc, None, vec![(clock, None, Some(1))])
436 /// .expect("this transition can be added");
437 /// pg_builder
438 /// .add_autonomous_timed_transition(initial_loc, initial_loc, Some(BooleanExpr::from(false)), vec![(clock, Some(1), None)])
439 /// .expect("this one too");
440 /// ```
441 #[inline(always)]
442 pub fn add_autonomous_timed_transition(
443 &mut self,
444 pre: Location,
445 post: Location,
446 guard: Option<PgGuard>,
447 constraints: Vec<(Clock, TimeRange)>,
448 ) -> Result<(), PgError> {
449 self.add_timed_transition(pre, EPSILON, post, guard, constraints)
450 }
451
452 /// Produces a [`ProgramGraph`] defined by the [`ProgramGraphBuilder`]'s data and consuming it.
453 ///
454 /// Since the construction of the builder is already checked ad every step,
455 /// this method cannot fail.
456 pub fn build(mut self) -> ProgramGraph {
457 // Since vectors of effects and transitions will become immutable,
458 // they should be shrunk to take as little space as possible
459 self.initial_states.shrink_to_fit();
460 self.vars.shrink_to_fit();
461 self.effects.iter_mut().for_each(|effect| {
462 if let Effect::Effects(_, resets) = effect {
463 resets.sort_unstable();
464 resets.shrink_to_fit();
465 }
466 });
467 self.effects.shrink_to_fit();
468 let mut locations = self
469 .locations
470 .into_iter()
471 .map(|(transitions, loc_invariants)| {
472 let mut transitions = Vec::from_iter(transitions);
473 transitions.shrink_to_fit();
474 transitions.iter_mut().for_each(|(_, loc_transitions)| {
475 assert!(!loc_transitions.is_empty());
476 loc_transitions.sort_unstable_by_key(|(p, ..)| *p);
477 loc_transitions.shrink_to_fit();
478 });
479 // Actions are assumed to be sorted
480 assert!(transitions.is_sorted_by_key(|(action, _)| *action));
481 (transitions, loc_invariants)
482 })
483 .collect::<Vec<_>>();
484 locations.shrink_to_fit();
485 // Build program graph
486 let pg = ProgramGraph {
487 initial_states: self.initial_states,
488 effects: self.effects,
489 locations,
490 vars: self.vars,
491 clocks: self.clocks,
492 };
493 info!(
494 "create Program Graph with: {} locations; {} actions; {} vars; size {}",
495 pg.locations.len(),
496 pg.effects.len(),
497 pg.vars.len(),
498 pg.get_size()
499 );
500 pg
501 }
502}