1
use std::collections::BTreeMap;
2
use std::path::PathBuf;
3
use std::string::ToString;
4
use std::sync::{Arc, Mutex, RwLock};
5

            
6
use conjure_cp::ast::{DeclarationKind, Literal, Name};
7
use conjure_cp::bug;
8
use conjure_cp::context::Context;
9

            
10
use serde_json::{Map, Value as JsonValue};
11

            
12
use itertools::Itertools as _;
13
use tempfile::tempdir;
14

            
15
use crate::utils::json::sort_json_object;
16
use conjure_cp::Model;
17
use conjure_cp::parse::tree_sitter::parse_essence_file;
18
use conjure_cp::solver::Solver;
19

            
20
use glob::glob;
21

            
22
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
23

            
24
3676
pub fn get_solutions(
25
3676
    solver: Solver,
26
3676
    model: Model,
27
3676
    num_sols: i32,
28
3676
    solver_input_file: &Option<PathBuf>,
29
3676
) -> Result<Vec<BTreeMap<Name, Literal>>, anyhow::Error> {
30
3676
    let adaptor_name = solver.get_name();
31

            
32
3676
    eprintln!("Building {adaptor_name} model...");
33

            
34
    // Create for later since we consume the model when loading it
35
3676
    let symbols_ptr = model.symbols_ptr_unchecked().clone();
36

            
37
3676
    let solver = solver.load_model(model)?;
38

            
39
3676
    if let Some(solver_input_file) = solver_input_file {
40
        eprintln!(
41
            "Writing solver input file to {}",
42
            solver_input_file.display()
43
        );
44
        let file = Box::new(std::fs::File::create(solver_input_file)?);
45
        solver.write_solver_input_file(&mut (file as Box<dyn std::io::Write>))?;
46
3676
    }
47

            
48
3676
    eprintln!("Running {adaptor_name}...");
49

            
50
    // Create two arcs, one to pass into the solver callback, one to get solutions out later
51
3676
    let all_solutions_ref = Arc::new(Mutex::<Vec<BTreeMap<Name, Literal>>>::new(vec![]));
52
3676
    let all_solutions_ref_2 = all_solutions_ref.clone();
53

            
54
3676
    let solver = if num_sols > 0 {
55
        // Get num_sols solutions
56
        let sols_left = Mutex::new(num_sols);
57

            
58
        #[allow(clippy::unwrap_used)]
59
        solver
60
            .solve(Box::new(move |sols| {
61
                let mut all_solutions = (*all_solutions_ref_2).lock().unwrap();
62
                (*all_solutions).push(sols.into_iter().collect());
63
                let mut sols_left = sols_left.lock().unwrap();
64
                *sols_left -= 1;
65

            
66
                *sols_left != 0
67
            }))
68
            .unwrap()
69
    } else {
70
        // Get all solutions
71
        #[allow(clippy::unwrap_used)]
72
3676
        solver
73
107952
            .solve(Box::new(move |sols| {
74
107952
                let mut all_solutions = (*all_solutions_ref_2).lock().unwrap();
75
107952
                (*all_solutions).push(sols.into_iter().collect());
76
107952
                true
77
107952
            }))
78
3676
            .unwrap()
79
    };
80

            
81
3676
    solver.save_stats_to_context();
82

            
83
    // Get the collections of solutions and model symbols
84
    #[allow(clippy::unwrap_used)]
85
3676
    let mut sols_guard = (*all_solutions_ref).lock().unwrap();
86
3676
    let sols = &mut *sols_guard;
87
3676
    let symbols = symbols_ptr.read();
88

            
89
    // Get the representations for each variable by name, since some variables are
90
    // divided into multiple auxiliary variables(see crate::representation::Representation)
91
3676
    let names = symbols.clone().into_iter().map(|x| x.0).collect_vec();
92
3676
    let representations = names
93
3676
        .into_iter()
94
169488
        .filter_map(|x| symbols.representations_for(&x).map(|repr| (x, repr)))
95
168576
        .filter_map(|(name, reprs)| {
96
168576
            if reprs.is_empty() {
97
166536
                return None;
98
2040
            }
99
2040
            assert!(
100
2040
                reprs.len() <= 1,
101
                "multiple representations for a variable is not yet implemented"
102
            );
103

            
104
2040
            assert_eq!(
105
2040
                reprs[0].len(),
106
                1,
107
                "nested representations are not yet implemented"
108
            );
109
2040
            Some((name, reprs[0][0].clone()))
110
168576
        })
111
3676
        .collect_vec();
112

            
113
107952
    for sol in sols.iter_mut() {
114
        // Get the value of complex variables using their auxiliary variables
115
134904
        for (name, representation) in representations.iter() {
116
134864
            let value = representation.value_up(sol).unwrap();
117
134864
            sol.insert(name.clone(), value);
118
134864
        }
119

            
120
        // Remove auxiliary variables since we've found the value of the
121
        // variable they represent
122
107952
        *sol = sol
123
107952
            .clone()
124
107952
            .into_iter()
125
26409596
            .filter(|(name, _)| {
126
26409596
                !matches!(name, Name::Represented(_)) && !matches!(name, Name::Machine(_))
127
26409596
            })
128
107952
            .collect();
129
    }
130

            
131
107952
    sols.retain(|x| !x.is_empty());
132
3676
    Ok(sols.clone())
133
3676
}
134

            
135
#[allow(clippy::unwrap_used)]
136
pub fn get_solutions_from_conjure(
137
    essence_file: &str,
138
    context: Arc<RwLock<Context<'static>>>,
139
) -> Result<Vec<BTreeMap<Name, Literal>>, anyhow::Error> {
140
    let tmp_dir = tempdir()?;
141

            
142
    let mut cmd = std::process::Command::new("conjure");
143
    let output = cmd
144
        .arg("solve")
145
        .arg("--number-of-solutions=all")
146
        .arg("--copy-solutions=no")
147
        .arg("-o")
148
        .arg(tmp_dir.path())
149
        .arg(essence_file)
150
        .output()?;
151

            
152
    if !output.status.success() {
153
        let stderr =
154
            String::from_utf8(output.stderr).unwrap_or_else(|e| e.utf8_error().to_string());
155
        return Err(anyhow::Error::msg(format!(
156
            "Error: `conjure solve` exited with code {}; stderr: {}",
157
            output.status, stderr
158
        )));
159
    }
160

            
161
    let solutions_files: Vec<_> =
162
        glob(&format!("{}/*.solution", tmp_dir.path().display()))?.collect();
163

            
164
    let solutions_set: Vec<_> = solutions_files
165
        .par_iter()
166
        .map(|solutions_file| {
167
            let solutions_file = solutions_file.as_ref().unwrap();
168
            let model = parse_essence_file(solutions_file.to_str().unwrap(), Arc::clone(&context))
169
                .expect("conjure solutions files to be parsable");
170

            
171
            let mut solutions = BTreeMap::new();
172
            for (name, decl) in model.symbols().clone().into_iter() {
173
                match &decl.kind() as &DeclarationKind {
174
                    conjure_cp::ast::DeclarationKind::ValueLetting(expression) => {
175
                        let literal = expression
176
                            .clone()
177
                            .into_literal()
178
                            .expect("lettings in a solution should only contain literals");
179
                        solutions.insert(name, literal);
180
                    }
181
                    _ => {
182
                        bug!("only expect value letting declarations in solutions")
183
                    }
184
                }
185
            }
186
            solutions
187
        })
188
        .collect();
189

            
190
    Ok(solutions_set
191
        .into_iter()
192
        .filter(|x| !x.is_empty())
193
        .collect())
194
}
195

            
196
7340
pub fn solutions_to_json(solutions: &Vec<BTreeMap<Name, Literal>>) -> JsonValue {
197
7340
    let mut json_solutions = Vec::new();
198
215888
    for solution in solutions {
199
215888
        let mut json_solution = Map::new();
200
715408
        for (var_name, constant) in solution {
201
715408
            let serialized_constant = serde_json::to_value(constant).unwrap();
202
715408
            json_solution.insert(var_name.to_string(), serialized_constant);
203
715408
        }
204
215888
        json_solutions.push(JsonValue::Object(json_solution));
205
    }
206
7340
    let ans = JsonValue::Array(json_solutions);
207
7340
    sort_json_object(&ans, true)
208
7340
}