1
#![forbid(unsafe_code)]
2

            
3
use merc_lts::LTS;
4
use merc_lts::LabelledTransitionSystem;
5
use merc_lts::LtsBuilderFast;
6
use merc_lts::StateIndex;
7
use merc_utilities::TagIndex;
8

            
9
use crate::BlockPartition;
10

            
11
/// A zero sized tag for the block.
12
pub struct BlockTag {}
13

            
14
/// The index for blocks.
15
pub type BlockIndex = TagIndex<usize, BlockTag>;
16

            
17
/// A trait for partition refinement algorithms that expose the block number for
18
/// every state. Can be used to compute the quotient labelled transition system.
19
///
20
/// The invariants are that the union of all blocks is the original set, and
21
/// that each block contains distinct elements
22
pub trait Partition {
23
    /// Returns the block number for the given state.
24
    fn block_number(&self, state_index: StateIndex) -> BlockIndex;
25

            
26
    /// Returns the number of blocks in the partition.
27
    fn num_of_blocks(&self) -> usize;
28

            
29
    /// Returns the number of elements in the partition.
30
    fn len(&self) -> usize;
31

            
32
    /// Returns whether the partition is empty.
33
    fn is_empty(&self) -> bool {
34
        self.len() == 0
35
    }
36
}
37

            
38
/// Returns a new LTS based on the given partition.
39
///
40
/// The naive version will add the transitions of all states in the block to the quotient LTS.
41
900
pub fn quotient_lts_naive<L: LTS>(
42
900
    lts: &L,
43
900
    partition: &impl Partition,
44
900
    eliminate_tau_loops: bool,
45
900
) -> LabelledTransitionSystem<L::Label> {
46
    // Introduce the transitions based on the block numbers, the number of blocks is a decent approximation for the number of transitions.
47
900
    let mut builder = LtsBuilderFast::with_capacity(
48
900
        lts.labels().into(),
49
900
        Vec::new(),
50
900
        partition.num_of_blocks(), // We expect one transition per state.
51
    );
52

            
53
7338
    for state_index in lts.iter_states() {
54
15728
        for transition in lts.outgoing_transitions(state_index) {
55
15728
            let block = partition.block_number(state_index);
56
15728
            let to_block = partition.block_number(transition.to);
57

            
58
            // If we eliminate tau loops then check if the 'to' and 'from' end up in the same block
59
15728
            if !(eliminate_tau_loops && lts.is_hidden_label(transition.label) && block == to_block) {
60
14370
                debug_assert!(
61
14370
                    partition.block_number(state_index) < partition.num_of_blocks(),
62
                    "Quotienting assumes that the block numbers do not exceed the number of blocks"
63
                );
64

            
65
14370
                builder.add_transition(
66
14370
                    StateIndex::new(block.value()),
67
14370
                    &lts.labels()[transition.label],
68
14370
                    StateIndex::new(to_block.value()),
69
                );
70
1358
            }
71
        }
72
    }
73

            
74
900
    builder.require_num_of_states(partition.num_of_blocks());
75
900
    builder.finish(
76
900
        StateIndex::new(partition.block_number(lts.initial_state_index()).value()),
77
        true,
78
    )
79
900
}
80

            
81
/// Optimised implementation for block partitions.
82
///
83
/// Chooses a single state in the block as representative. If BRANCHING then the chosen state is a bottom state.
84
100
pub fn quotient_lts_block<L: LTS, const BRANCHING: bool>(
85
100
    lts: &L,
86
100
    partition: &BlockPartition,
87
100
) -> LabelledTransitionSystem<L::Label> {
88
100
    let mut builder = LtsBuilderFast::new(lts.labels().into(), Vec::new());
89

            
90
13210
    for block in (0..partition.num_of_blocks()).map(BlockIndex::new) {
91
        // Pick any state in the block
92
13210
        let mut candidate = if let Some(state) = partition.iter_block(block).next() {
93
13210
            state
94
        } else {
95
            panic!("Blocks in the partition should not be empty {}", block);
96
        };
97

            
98
13210
        if BRANCHING {
99
            // DFS into a bottom state.
100
            let mut found = false;
101
            while !found {
102
                found = true;
103

            
104
                if let Some(trans) = lts
105
                    .outgoing_transitions(candidate)
106
                    .find(|trans| lts.is_hidden_label(trans.label) && partition.block_number(trans.to) == block)
107
                {
108
                    found = false;
109
                    candidate = trans.to;
110
                }
111
            }
112
13210
        }
113

            
114
        // Add all transitions from the representative state.
115
75736
        for transition in lts.outgoing_transitions(candidate) {
116
75736
            if BRANCHING {
117
                // Candidate is a bottom state, so add all transitions.
118
                debug_assert!(
119
                    !(lts.is_hidden_label(transition.label) && partition.block_number(transition.to) == block),
120
                    "This state is not bottom {}",
121
                    block
122
                );
123
75736
            }
124

            
125
75736
            builder.add_transition(
126
75736
                StateIndex::new(*block),
127
75736
                &lts.labels()[transition.label],
128
75736
                StateIndex::new(*partition.block_number(transition.to)),
129
            );
130
        }
131
    }
132

            
133
100
    builder.require_num_of_states(partition.num_of_blocks());
134
100
    builder.finish(
135
100
        StateIndex::new(partition.block_number(lts.initial_state_index()).value()),
136
        true,
137
    )
138
100
}