momba_explore/explore/
mod.rs

1//! Algorithms and data structures for state space exploration.
2
3use serde::{Deserialize, Serialize};
4
5use itertools::Itertools;
6
7use super::model;
8use super::time;
9
10mod actions;
11mod compiled;
12mod evaluate;
13
14pub mod external;
15
16use compiled::*;
17
18pub use actions::*;
19
20/// A *state* of an automaton network.
21///
22/// Every state keeps track of the locations the automata of the network are in, the
23/// values of the global variables, and a potentially infinite set of clock valuations
24/// using the respective [TimeType][time::TimeType].
25#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
26pub struct State<V> {
27    locations: Box<[LocationIndex]>,
28    global_store: Box<[model::Value]>,
29    transient_store: Box<[model::Value]>,
30    valuations: V,
31}
32
33impl<V> State<V> {
34    /// Returns the name of the location the automaton with the provided name is in.
35    ///
36    /// Panics in case the state has not been produced by the provided explorer or there
37    /// is no automaton with the provided name in the automaton network.
38    pub fn get_location_of<'e, T: time::TimeType>(
39        &self,
40        explorer: &'e Explorer<T>,
41        automaton_name: &str,
42    ) -> &'e String {
43        explorer
44            .network
45            .automata
46            .get_index_of(automaton_name)
47            .and_then(|automaton_index| {
48                self.locations
49                    .get(automaton_index)
50                    .and_then(|location_index| {
51                        let (_, automaton) = explorer
52                            .network
53                            .automata
54                            .get_index(automaton_index)
55                            .unwrap();
56                        automaton
57                            .locations
58                            .get_index(*location_index)
59                            .map(|(location_name, _)| location_name)
60                    })
61            })
62            .expect("Invalid automaton name or explorer passed to `State::get_location_of`.")
63    }
64
65    /// Returns the value of the provided global variable.
66    ///
67    /// Panics in case the state has not been produced by the provided explorer or there
68    /// is no global variable with the provided name in the automaton network.
69    pub fn get_global_value<T: time::TimeType>(
70        &self,
71        explorer: &Explorer<T>,
72        identifier: &str,
73    ) -> &model::Value {
74        explorer
75            .network
76            .declarations
77            .global_variables
78            .get_index_of(identifier)
79            .and_then(|index| self.global_store.get(index))
80            .expect("Invalid variable name or explorer passed to `State::get_global_value`.")
81    }
82
83    /// Returns the value of the provided transient variable.
84    ///
85    /// Panics in case the state has not been produced by the provided explorer or there
86    /// is no transient variable with the provided name in the automaton network.
87    pub fn get_transient_value(&self, network: &model::Network, identifier: &str) -> &model::Value {
88        network
89            .declarations
90            .transient_variables
91            .get_index_of(identifier)
92            .and_then(|index| self.transient_store.get(index))
93            .expect("Invalid variable name or explorer passed to `State::get_transient_value`.")
94    }
95
96    /// Returns the clock valuations associated with the state.
97    pub fn valuations(&self) -> &V {
98        &self.valuations
99    }
100
101    /// Constructs a global evaluation environment from the state.
102    fn global_env(&self) -> GlobalEnvironment {
103        evaluate::Environment::new([&self.global_store, &self.transient_store])
104    }
105
106    /// Constructs an edge evaluation environment from the state and edge store.
107    fn edge_env<'e, 's: 'e>(&'s self, edge_store: &'e [model::Value]) -> EdgeEnvironment<'e> {
108        evaluate::Environment::new([&self.global_store, &self.transient_store, edge_store])
109    }
110
111    fn future<T: time::TimeType>(
112        state: State<T::Valuations>,
113        compiled_network: &CompiledNetwork<T>,
114    ) -> State<T::Valuations> {
115        let env = evaluate::Environment::new([&state.global_store, &state.transient_store]);
116
117        let mut valuations = compiled_network.zone_compiler.future(state.valuations);
118
119        for (automaton_index, location_index) in state.locations.iter().enumerate() {
120            let location = &compiled_network.automata[automaton_index].locations[*location_index];
121            valuations = location
122                .invariant
123                .iter()
124                .fold(valuations, |valuations, constraint| {
125                    compiled_network.zone_compiler.constrain(
126                        valuations,
127                        &constraint.difference,
128                        constraint.is_strict,
129                        constraint.bound.evaluate(&env),
130                    )
131                })
132        }
133
134        State {
135            locations: state.locations,
136            global_store: state.global_store,
137            transient_store: state.transient_store,
138            valuations: valuations,
139        }
140    }
141}
142
143/// A *transition* of an automaton network.
144pub struct Transition<'e, T: time::TimeType> {
145    pub(crate) edges: Box<[&'e CompiledEdge<T>]>,
146    pub(crate) actions: Box<[Action]>,
147    pub(crate) valuations: T::Valuations,
148    pub(crate) action: Action,
149}
150
151impl<'e, T: time::TimeType> Transition<'e, T> {
152    /// Returns a slice of actions the participating automata perform.
153    pub fn local_actions(&self) -> &[Action] {
154        &self.actions
155    }
156
157    /// Returns a vector of indices of the participating automata.
158    pub fn edges(&self) -> Vec<model::EdgeReference> {
159        self.edges
160            .iter()
161            .map(|edge| edge.reference.clone())
162            .collect()
163    }
164
165    /// Returns the clock valuations for which the transition is performed.
166    pub fn valuations(&self) -> &T::Valuations {
167        &self.valuations
168    }
169
170    /// Returns the resulting action.
171    pub fn result_action(&self) -> &Action {
172        &self.action
173    }
174
175    /// Replaces the valuations of the transition.
176    pub fn replace_valuations(self, valuations: T::Valuations) -> Self {
177        Transition {
178            edges: self.edges,
179            actions: self.actions,
180            valuations,
181            action: self.action,
182        }
183    }
184
185    pub fn edge_references(&self) -> Vec<model::EdgeReference> {
186        self.edges
187            .iter()
188            .map(|edge| edge.reference.clone())
189            .collect()
190    }
191}
192
193/// A *destination* of a transition.
194pub struct Destination<'c, T: time::TimeType> {
195    pub(crate) probability: f64,
196    pub(crate) destinations: Box<[&'c CompiledDestination<T>]>,
197}
198
199impl<'c, T: time::TimeType> Destination<'c, T> {
200    /// Returns the probability of the destination.
201    pub fn probability(&self) -> f64 {
202        self.probability
203    }
204}
205
206/// A state space explorer for a particular automaton network.
207pub struct Explorer<T: time::TimeType> {
208    pub network: model::Network,
209    pub(crate) compiled_network: CompiledNetwork<T>,
210}
211
212impl<T: time::TimeType> Explorer<T> {
213    /// Constructs a new state space explorer from the provided network.
214    pub fn new(network: model::Network) -> Self {
215        let compiled = CompiledNetwork::new(&network);
216        Explorer {
217            network,
218            compiled_network: compiled,
219        }
220    }
221
222    /// Returns a vector of initial states of the network.
223    pub fn initial_states(&self) -> Vec<State<T::Valuations>> {
224        let global_scope = self.network.global_scope();
225        self.network
226            .initial_states
227            .iter()
228            .map(|state| {
229                let locations = self
230                    .network
231                    .automata
232                    .iter()
233                    .map(|(automaton_name, automaton)| {
234                        automaton
235                            .locations
236                            .get_index_of(&state.locations[automaton_name])
237                            .unwrap()
238                    })
239                    .collect();
240                let global_store: Box<[model::Value]> = self
241                    .network
242                    .declarations
243                    .global_variables
244                    .keys()
245                    .map(|identifier| state.values[identifier].clone())
246                    .collect();
247                let transient_store = self
248                    .compiled_network
249                    .compute_transient_values(&global_store);
250                let global_env = evaluate::Environment::new([&global_store, &transient_store]);
251                // FIXME: explore the future of this state
252                let valuations = self
253                    .compiled_network
254                    .zone_compiler
255                    .create_valuations(
256                        state
257                            .zone
258                            .iter()
259                            .map(|constraint| {
260                                CompiledClockConstraint::compile(
261                                    &self.compiled_network.zone_compiler,
262                                    constraint,
263                                    &global_scope,
264                                )
265                                .evaluate(&global_env)
266                            })
267                            .collect(),
268                    )
269                    .unwrap();
270                State::<T::Valuations>::future(
271                    State {
272                        locations,
273                        global_store,
274                        transient_store,
275                        valuations,
276                    },
277                    &self.compiled_network,
278                )
279            })
280            .collect()
281    }
282
283    /// Returns a vector of outgoing transitions of the given state.
284    pub fn transitions<'c>(&'c self, state: &State<T::Valuations>) -> Vec<Transition<'c, T>> {
285        let global_env = state.global_env();
286        let enabled_edges = self
287            .compiled_network
288            .automata
289            .iter()
290            .zip(state.locations.iter())
291            .map(|(automaton, location_index)| {
292                automaton.locations[*location_index]
293                    .visible_edges
294                    .iter()
295                    .map(|link_edges| {
296                        link_edges
297                            .iter()
298                            .filter(|edge| edge.base.is_enabled(&global_env))
299                            .collect()
300                    })
301                    .collect()
302            })
303            .collect();
304        self.compiled_network
305            .automata
306            .iter()
307            .enumerate()
308            .map(|(index, automaton)| {
309                let location = &automaton.locations[state.locations[index]];
310                location.internal_edges.iter().filter_map(|edge| {
311                    if !edge.is_enabled(&global_env) {
312                        None
313                    } else {
314                        // We may want to improve the efficiency of this function in the future.
315                        //
316                        // Instead of applying each constraint individually applying them in bulk
317                        // makes canonicalization more efficient for clock zones.
318                        let valuations = edge.guard.clock_constraints.iter().fold(
319                            state.valuations.clone(),
320                            |valuations, constraint| {
321                                self.compiled_network.zone_compiler.constrain(
322                                    valuations,
323                                    &constraint.difference,
324                                    constraint.is_strict,
325                                    constraint.bound.evaluate(&global_env),
326                                )
327                            },
328                        );
329                        if self.compiled_network.zone_compiler.is_empty(&valuations) {
330                            None
331                        } else {
332                            Some(Transition {
333                                edges: Box::new([edge]),
334                                actions: Box::new([Action::Silent]),
335                                valuations,
336                                action: Action::Silent,
337                            })
338                        }
339                    }
340                })
341            })
342            .flatten()
343            .chain(
344                self.compiled_network
345                    .links
346                    .iter()
347                    .map(|link| {
348                        link.sync_vector
349                            .iter()
350                            .map(|vector_item| {
351                                vector_item.compute_link_edges(&global_env, &enabled_edges)
352                            })
353                            .collect::<Vec<_>>()
354                            .iter()
355                            .multi_cartesian_product()
356                            .filter_map(|edges| {
357                                self.compiled_network.compute_transition(
358                                    &state.valuations,
359                                    &global_env,
360                                    link,
361                                    &edges,
362                                )
363                            })
364                            .collect::<Vec<_>>()
365                    })
366                    .flatten(),
367            )
368            .collect()
369    }
370
371    /// Returns a vector of destinations for a given transition.
372    ///
373    /// Panics if the transition has not been generated from the provided state.
374    pub fn destinations<'c>(
375        &'c self,
376        state: &State<T::Valuations>,
377        transition: &Transition<'c, T>,
378    ) -> Vec<Destination<'c, T>> {
379        transition
380            .edges
381            .iter()
382            .map(|edge| edge.destinations.iter())
383            .multi_cartesian_product()
384            .map(|destinations| Destination {
385                probability: transition.actions.iter().zip(destinations.iter()).fold(
386                    1.0,
387                    |probability, (action, destination)| {
388                        let edge_env = state.edge_env(action.arguments());
389                        probability
390                            * destination
391                                .probability
392                                .evaluate(&edge_env)
393                                .unwrap_float64()
394                                .into_inner()
395                    },
396                ),
397                destinations: destinations.into(),
398            })
399            .collect()
400    }
401
402    /// Returns the successor state reached via a destination.
403    ///
404    /// Panics if the destination has not been generated from the provided state and transition.
405    pub fn successor<'c>(
406        &'c self,
407        state: &State<T::Valuations>,
408        transition: &Transition<'c, T>,
409        destination: &Destination<T>,
410    ) -> State<T::Valuations> {
411        let mut targets = vec![
412            model::Value::Vector(state.global_store.clone().into()),
413            model::Value::Vector(state.transient_store.clone().into()),
414        ];
415        for index in 0..self.compiled_network.assignment_groups.len() {
416            let global_store = targets[0].clone();
417            for (action, automaton_destination) in transition
418                .actions
419                .iter()
420                .zip(destination.destinations.iter())
421            {
422                let env = EdgeEnvironment::new([
423                    &global_store.unwrap_vector(),
424                    &state.transient_store,
425                    action.arguments(),
426                ]);
427                for assignment in automaton_destination.assignments[index].iter() {
428                    let value = assignment.value.evaluate(&env);
429                    assignment.target.evaluate(&mut targets, &env).store(value);
430                }
431            }
432        }
433
434        let mut locations = state.locations.clone();
435        let mut valuations = transition.valuations().clone();
436        for automaton_destination in destination.destinations.iter() {
437            let automaton_index = automaton_destination.automaton_index;
438            locations[automaton_index] = automaton_destination.location;
439            valuations = self
440                .compiled_network
441                .zone_compiler
442                .reset(valuations, &automaton_destination.reset);
443        }
444
445        let transient_store = self
446            .compiled_network
447            .compute_transient_values(&targets[0].unwrap_vector());
448
449        let state = State::<T::Valuations>::future(
450            State {
451                locations,
452                global_store: targets[0].unwrap_vector().clone().into(),
453                transient_store,
454                valuations,
455            },
456            &self.compiled_network,
457        );
458
459        // The truth of the guard should imply the truth of the invariants.
460        assert!(!self
461            .compiled_network
462            .zone_compiler
463            .is_empty(&state.valuations));
464
465        state
466    }
467
468    pub fn externalize_transition(&self, transition: &Transition<T>) -> external::Transition<T> {
469        external::Transition {
470            edge_vector: transition
471                .edges
472                .iter()
473                .map(|edge| edge.reference.clone())
474                .collect(),
475            action_vector: transition.actions.clone(),
476            action: transition.action.clone(),
477            valuations: self
478                .compiled_network
479                .zone_compiler
480                .externalize(transition.valuations.clone()),
481        }
482    }
483}
484
485/// A specialization of [Explorer] for MDPs using [NoClocks][time::NoClocks].
486///
487/// MDPs do not have any real-valued clocks.
488pub type MDPExplorer = Explorer<time::NoClocks>;