1
use std::fmt;
2

            
3
use merc_data::DataVariable;
4
use merc_utilities::MercError;
5
use oxidd::ManagerRef;
6
use oxidd::ldd::LDDFunction;
7
use oxidd::ldd::LDDManagerRef;
8
use oxidd::ldd::Value;
9

            
10
use crate::DependencyGraph;
11
use crate::Relation;
12

            
13
/// A generic trait for symbolic LDD-based state-space exploration.
14
///
15
/// Any type with an initial state and transition groups can be explored via [crate::reachability].
16
pub trait SymbolicLPS {
17
    /// The transition group type backing this LTS.
18
    type Group: TransitionGroup;
19

            
20
    /// Returns the LDD representing the initial state(s).
21
    fn initial_state(&self) -> &LDDFunction;
22

            
23
    /// Returns the transition groups.
24
    fn transition_groups(&self) -> &[Self::Group];
25

            
26
    /// Returns the transition groups (mutable), needed for on-the-fly learning.
27
    fn transition_groups_mut(&mut self) -> &mut [Self::Group];
28

            
29
    /// Creates a fresh learning context, threaded through [TransitionGroup::learn_successors].
30
    ///
31
    /// The context owns the per-run mutable state shared by all groups (e.g. an enumeration backend
32
    /// and value interning), so the groups themselves need no interior mutability. It is created once
33
    /// per reachability run and must outlive every learning call.
34
    fn create_context(&self) -> <Self::Group as TransitionGroup>::Context;
35

            
36
    /// Computes the dependency graph from the read/write indices of each transition group.
37
    fn dependency_graph(&self) -> DependencyGraph {
38
        let relations = self
39
            .transition_groups()
40
            .iter()
41
            .map(|group| {
42
                Relation::new(
43
                    group.read_indices().iter().map(|i| *i as usize).collect(),
44
                    group.write_indices().iter().map(|i| *i as usize).collect(),
45
                )
46
            })
47
            .collect();
48

            
49
        DependencyGraph::new(relations)
50
    }
51
}
52

            
53
/// A single LDD-based transition group for a set of summands (short vector encoding).
54
pub trait TransitionGroup: fmt::Debug {
55
    /// Per-run mutable state threaded into [Self::learn_successors]; `()` when nothing is learned.
56
    type Context;
57

            
58
    /// Returns the transition relation T' -> U' for this summand group.
59
    fn relation(&self) -> &LDDFunction;
60

            
61
    /// Returns the read indices for this summand group.
62
    fn read_indices(&self) -> &[u32];
63

            
64
    /// Returns the write indices for this summand group.
65
    fn write_indices(&self) -> &[u32];
66

            
67
    /// Returns the action label index for this summand group, if any.
68
    fn action_label_index(&self) -> Option<usize>;
69

            
70
    /// Returns the meta LDD required to perform the relational product.
71
    fn meta(&self) -> &LDDFunction;
72

            
73
    /// Learns the successors of `todo` and incorporates them into [Self::relation].
74
    fn learn_successors(
75
        &mut self,
76
        context: &mut Self::Context,
77
        manager: &LDDManagerRef,
78
        todo: &LDDFunction,
79
    ) -> Result<(), MercError>;
80
}
81

            
82
/// A short vector transition relation for a group of summands.
83
///
84
/// A short transition vector is part of a transition relation T -> U, where we
85
/// store T' -> U' with T' being the projection of T on the read parameters and
86
/// U' the projection of U on the write parameters, as a LDD. Formally,
87
///
88
/// (t, u) in (T -> U)  iff  (t', u') in (T' -> U') where t' and u' are the projections
89
///     of t and u on the read and write parameters respectively.
90
pub struct SummandGroup {
91
    read_parameters: Vec<DataVariable>,
92
    read_parameter_indices: Vec<Value>,
93

            
94
    write_parameters: Vec<DataVariable>,
95
    write_parameter_indices: Vec<Value>,
96

            
97
    relation: LDDFunction,
98
    meta: LDDFunction,
99
    action_label_index: usize,
100
}
101

            
102
impl SummandGroup {
103
    /// Creates a new summand group.
104
    ///
105
    /// Fails if a read or write parameter is not found in `parameters`.
106
2193
    pub fn new(
107
2193
        manager: &LDDManagerRef,
108
2193
        parameters: &[DataVariable],
109
2193
        read_parameters: Vec<DataVariable>,
110
2193
        write_parameters: Vec<DataVariable>,
111
2193
        relation: LDDFunction,
112
2193
    ) -> Result<Self, MercError> {
113
2193
        let mut read_parameter_indices: Vec<Value> = read_parameters
114
2193
            .iter()
115
9466
            .map(|var| {
116
9466
                parameters
117
9466
                    .iter()
118
54790
                    .position(|p| p == var)
119
9466
                    .ok_or(format!("Cannot find read parameter {var:?}"))
120
9466
                    .map(|pos| pos as Value)
121
9466
            })
122
2193
            .collect::<Result<Vec<Value>, _>>()?;
123

            
124
2193
        let mut write_parameter_indices: Vec<Value> = write_parameters
125
2193
            .iter()
126
9972
            .map(|var| {
127
9972
                parameters
128
9972
                    .iter()
129
58262
                    .position(|p| p == var)
130
9972
                    .ok_or(format!("Cannot find write parameter {var:?}"))
131
9972
                    .map(|pos| pos as Value)
132
9972
            })
133
2193
            .collect::<Result<Vec<Value>, _>>()?;
134

            
135
2193
        write_parameter_indices.sort_unstable();
136
2193
        read_parameter_indices.sort_unstable();
137

            
138
2193
        let meta = manager
139
2193
            .with_manager_shared(|m| {
140
2193
                LDDFunction::relation_product_meta(m, &read_parameter_indices, &write_parameter_indices)
141
2193
            })?
142
            .meta;
143
2193
        let action_label_index = read_parameters.len() + write_parameters.len();
144

            
145
2193
        Ok(Self {
146
2193
            read_parameters,
147
2193
            read_parameter_indices,
148
2193
            write_parameters,
149
2193
            write_parameter_indices,
150
2193
            relation,
151
2193
            meta,
152
2193
            action_label_index,
153
2193
        })
154
2193
    }
155

            
156
    /// Returns the read parameters for this summand group.
157
    pub fn read_parameters(&self) -> &[DataVariable] {
158
        &self.read_parameters
159
    }
160

            
161
    /// Returns the write parameters for this summand group.
162
    pub fn write_parameters(&self) -> &[DataVariable] {
163
        &self.write_parameters
164
    }
165
}
166

            
167
impl TransitionGroup for SummandGroup {
168
    type Context = ();
169

            
170
149584
    fn relation(&self) -> &LDDFunction {
171
149584
        &self.relation
172
149584
    }
173

            
174
8810
    fn meta(&self) -> &LDDFunction {
175
8810
        &self.meta
176
8810
    }
177

            
178
1388695
    fn read_indices(&self) -> &[u32] {
179
1388695
        &self.read_parameter_indices
180
1388695
    }
181

            
182
584067
    fn write_indices(&self) -> &[u32] {
183
584067
        &self.write_parameter_indices
184
584067
    }
185

            
186
542535
    fn action_label_index(&self) -> Option<usize> {
187
542535
        Some(self.action_label_index)
188
542535
    }
189

            
190
8810
    fn learn_successors(
191
8810
        &mut self,
192
8810
        _context: &mut (),
193
8810
        _storage: &LDDManagerRef,
194
8810
        _todo: &LDDFunction,
195
8810
    ) -> Result<(), MercError> {
196
8810
        Ok(())
197
8810
    }
198
}
199

            
200
impl fmt::Debug for SummandGroup {
201
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202
        write!(
203
            f,
204
            "{:?} -> {:?}",
205
            self.read_parameter_indices, self.write_parameter_indices
206
        )
207
    }
208
}