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
//! Interleaved transition guided reduction algorithm.
//!
//! Implements interleaved transition guided reduction. This technique does not remove
//! all non-attractor states, but can very significantly prune the state space in
//! a very reasonable amount of time.
//!

use biodivine_lib_param_bn::symbolic_async_graph::{GraphColoredVertices, SymbolicAsyncGraph};
use biodivine_lib_param_bn::VariableId;

mod _impl_extended_component_process;
mod _impl_fwd_bwd_process;
mod _impl_reachable_process;
mod _impl_scheduler;

/// Removes from `initial` as many non-attractor states as possible
/// using interleaved transition guided reduction.
///
/// It also returns a list of system variables for which there are still
/// transitions in the graph (other variables are effectively constant).
///
/// If cancelled, the result is still valid, but not necessarily complete.
pub fn interleaved_transition_guided_reduction(
    graph: &SymbolicAsyncGraph,
    initial: GraphColoredVertices,
) -> (GraphColoredVertices, Vec<VariableId>) {
    let variables = graph.variables().collect::<Vec<_>>();
    let mut scheduler = Scheduler::new(initial, variables);
    for variable in graph.variables() {
        scheduler.spawn(ReachableProcess::new(
            variable,
            graph,
            scheduler.get_universe().clone(),
        ));
    }

    while !scheduler.is_done() {
        scheduler.step(graph);
    }

    scheduler.finalize()
}

/// **(internal)** A process trait is a unit of work that is managed by a `Scheduler`.
/// Process has a *weight* that approximates how symbolically hard is to work with
/// its intermediate representation.
trait Process {
    /// Perform one step in the process. This can perform multiple symbolic operations,
    /// but should be fairly simple (i.e. does not need interrupting).
    ///
    /// Returns true if the process cannot perform more steps.
    fn step(&mut self, scheduler: &mut Scheduler, graph: &SymbolicAsyncGraph) -> bool;

    /// Approximate symbolic complexity of the process.
    fn weight(&self) -> usize;

    /// Mark the given set of states as eliminated - i.e. they can be disregarded by this process.
    fn discard_states(&mut self, set: &GraphColoredVertices);
}

/// **(internal)** Scheduler manages work divided into `Processes`. It keeps a `universe`
/// of unprocessed vertices and a list of remaining active variables.
struct Scheduler {
    active_variables: Vec<VariableId>,
    universe: GraphColoredVertices,
    processes: Vec<(usize, Box<dyn Process>)>,
    to_discard: Option<GraphColoredVertices>,
}

/// **(internal)** Basic backward reachability process.
struct BwdProcess {
    bwd: GraphColoredVertices,
    universe: GraphColoredVertices,
}

/// **(internal)** Basic forward reachability process.
struct FwdProcess {
    fwd: GraphColoredVertices,
    universe: GraphColoredVertices,
}

/// **(internal)** Computes the set of vertices reachable from states that can perform `var_post`.
///
/// When reachable set is computed, it automatically starts the extended component process.
struct ReachableProcess {
    variable: VariableId,
    fwd: FwdProcess,
}

/// **(internal)** Computes the extended component of a forward-reachable set.
struct ExtendedComponentProcess {
    variable: VariableId,
    fwd_set: GraphColoredVertices,
    bwd: BwdProcess,
}