1
/// This example shows how to run a basic model top to bottom with Minion, with a focus on
2
/// demonstrating how the solver interface works.
3
///
4
/// The model is `conjure_oxide/tests/integration/basic/div/05/div-05.essence`
5
use conjure_core::{
6
    ast::{Literal, Name},
7
    solver::{adaptors::Minion, states::ExecutionSuccess},
8
};
9
use conjure_oxide::defaults::get_default_rule_sets;
10
use itertools::Itertools;
11
use std::collections::HashMap;
12

            
13
#[allow(clippy::unwrap_used)]
14
pub fn main() {
15
    use conjure_core::solver::SolverFamily;
16
    use conjure_core::{parse::get_example_model, rule_engine::resolve_rule_sets};
17
    use conjure_core::{
18
        rule_engine::rewrite_model,
19
        solver::{adaptors, Solver},
20
    };
21
    use std::sync::{Arc, Mutex};
22

            
23
    // Load an example model and rewrite it with conjure oxide.
24
    let model = get_example_model("div-05").unwrap();
25
    println!("Input model: \n {} \n", model.constraints);
26

            
27
    // TODO: We will have a nicer way to do this in the future
28
    let rule_sets = resolve_rule_sets(SolverFamily::Minion, &get_default_rule_sets()).unwrap();
29

            
30
    let model = rewrite_model(&model, &rule_sets).unwrap();
31
    println!("Rewritten model: \n {} \n", model.constraints);
32

            
33
    // To tell the `Solver` type what solver to use, you pass it a `SolverAdaptor`.
34
    // Here we use Minion.
35

            
36
    let solver = Solver::new(adaptors::Minion::new());
37

            
38
    // This API has a specific order:
39
    //
40
    // 1. Load a model
41
    // 2. Solve the model
42
    // 3. Read execution statistics
43
    //
44
    // If you skip a step, you get a compiler error!
45
    //
46
    // Solver has two type variables. One is the solver adaptor, the other is a state. This state
47
    // represents which step we are on. Certain methods are only available in certain states.
48

            
49
    // 1. Load a model
50
    // ===============
51
    //
52
    // Here, the solver takes in a subset of our model types and converts it into its own
53
    // representation. If it sees features it doesn't support, it will fail!.
54
    //
55
    // TRY: deleting this line! What compiler errors appear?
56
    // TRY: this takes the same `conjure_core::ast::Model` type as the rest of the program.
57
    //      what happens if we pass it a non re-written model?
58

            
59
    let solver = solver.load_model(model).unwrap();
60

            
61
    // 2. Solve
62
    // ========
63
    //
64
    //
65
    // To solve a model, we need to provide a callback function to be run whenever the solver has
66
    // found a solution. This takes a `HashMap<Name,Literal>`, representing a single solution, as
67
    // input.  The return value tells the solver whether to continue or not.
68
    //
69
    // We need this for the following:
70
    //
71
    //  1. To get solutions out of the solver
72
    //  2. To terminate the solver (e.g. if we only want 1 solution).
73
    //
74
    //
75
    // Concurrency
76
    // -----------
77
    //
78
    // The solver interface is designed to allow adaptors to use multiple-threads / processes if
79
    // necessary. Therefore, the callback type requires all variables inside it to have a static
80
    // lifetime and to implement Send (i.e. the variable can be safely shared between theads).
81

            
82
    // Here we will count solutions as well as returning the results.
83

            
84
    // We use Arc<Mutex<i32>> to create multiple pointers to a thread-safe mutable counter.
85
    let counter_ptr = Arc::new(Mutex::new(0));
86
    let counter_ptr_2 = counter_ptr.clone();
87

            
88
    // Doing the same for our list of solutions
89
    let all_solutions_ptr = Arc::new(Mutex::<Vec<HashMap<Name, Literal>>>::new(vec![]));
90
    let all_solutions_ptr_2 = all_solutions_ptr.clone();
91

            
92
    // Using the move |x| ... closure syntax, we give ownership of one of these pointers to the
93
    // solver. We still own the second pointer, which we use to get the counter out later!
94

            
95
    let result = solver.solve(Box::new(move |sols| {
96
        // add to counter
97
        let mut counter = (*counter_ptr_2).lock().unwrap();
98
        *counter += 1;
99

            
100
        // add to solutions
101
        let mut all_solutions = (*all_solutions_ptr_2).lock().unwrap();
102
        (*all_solutions).push(sols);
103
        true
104
    }));
105

            
106
    // Did the solver run successfully?
107
    let solver: Solver<Minion, ExecutionSuccess> = match result {
108
        Ok(s) => s,
109
        Err(e) => {
110
            panic!("Error! {:?}", e);
111
        }
112
    };
113

            
114
    // Read our counter.
115
    let counter = (*counter_ptr).lock().unwrap();
116
    println!("Num solutions: {}\n", counter);
117

            
118
    // Read solutions, print 3
119
    let all_sols = (*all_solutions_ptr).lock().unwrap();
120
    for (i, sols) in all_sols.iter().enumerate() {
121
        if i > 2 {
122
            println!("... and {} more", *counter - i);
123
            break;
124
        }
125
        println!("Solution {}:", i + 1);
126
        for (k, v) in sols.iter().sorted_by_key(|x| x.0) {
127
            println!("  {} = {}", k, v);
128
        }
129
        println!()
130
    }
131
    println!();
132

            
133
    // 3. Stats
134
    // Now that we have run the solver, we have access to the stats!
135
    // we can turn these into JSON for easy processing.
136
    //
137
    // TRY: what happens if we call solver.stats() when we haven't run the solver yet?
138
    let stats_json = serde_json::to_string_pretty(&solver.stats()).unwrap();
139
    println!("Solver stats: \n{}", stats_json);
140
}