Skip to main content

conjure_cp_cli/utils/
conjure.rs

1use std::collections::BTreeMap;
2use std::path::PathBuf;
3use std::string::ToString;
4use std::sync::{Arc, Mutex, RwLock};
5
6use conjure_cp::ast::{DeclarationKind, Literal, Name};
7use conjure_cp::bug;
8use conjure_cp::context::Context;
9
10use serde_json::{Map, Value as JsonValue};
11
12use itertools::Itertools as _;
13use tempfile::tempdir;
14
15use crate::utils::json::sort_json_object;
16use conjure_cp::Model;
17use conjure_cp::parse::tree_sitter::parse_essence_file;
18use conjure_cp::solver::Solver;
19
20use glob::glob;
21
22use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
23
24pub fn get_solutions(
25    solver: Solver,
26    model: Model,
27    num_sols: i32,
28    solver_input_file: &Option<PathBuf>,
29) -> Result<Vec<BTreeMap<Name, Literal>>, anyhow::Error> {
30    let adaptor_name = solver.get_name();
31
32    eprintln!("Building {adaptor_name} model...");
33
34    // Create for later since we consume the model when loading it
35    let symbols_ptr = model.symbols_ptr_unchecked().clone();
36
37    let solver = solver.load_model(model)?;
38
39    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    }
47
48    eprintln!("Running {adaptor_name}...");
49
50    // Create two arcs, one to pass into the solver callback, one to get solutions out later
51    let all_solutions_ref = Arc::new(Mutex::<Vec<BTreeMap<Name, Literal>>>::new(vec![]));
52    let all_solutions_ref_2 = all_solutions_ref.clone();
53
54    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        solver
73            .solve(Box::new(move |sols| {
74                let mut all_solutions = (*all_solutions_ref_2).lock().unwrap();
75                (*all_solutions).push(sols.into_iter().collect());
76                true
77            }))
78            .unwrap()
79    };
80
81    solver.save_stats_to_context();
82
83    // Get the collections of solutions and model symbols
84    #[allow(clippy::unwrap_used)]
85    let mut sols_guard = (*all_solutions_ref).lock().unwrap();
86    let sols = &mut *sols_guard;
87    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    let names = symbols.clone().into_iter().map(|x| x.0).collect_vec();
92    let representations = names
93        .into_iter()
94        .filter_map(|x| symbols.representations_for(&x).map(|repr| (x, repr)))
95        .filter_map(|(name, reprs)| {
96            if reprs.is_empty() {
97                return None;
98            }
99            assert!(
100                reprs.len() <= 1,
101                "multiple representations for a variable is not yet implemented"
102            );
103
104            assert_eq!(
105                reprs[0].len(),
106                1,
107                "nested representations are not yet implemented"
108            );
109            Some((name, reprs[0][0].clone()))
110        })
111        .collect_vec();
112
113    for sol in sols.iter_mut() {
114        // Get the value of complex variables using their auxiliary variables
115        for (name, representation) in representations.iter() {
116            let value = representation.value_up(sol).unwrap();
117            sol.insert(name.clone(), value);
118        }
119
120        // Remove auxiliary variables since we've found the value of the
121        // variable they represent
122        *sol = sol
123            .clone()
124            .into_iter()
125            .filter(|(name, _)| {
126                !matches!(name, Name::Represented(_)) && !matches!(name, Name::Machine(_))
127            })
128            .collect();
129    }
130
131    sols.retain(|x| !x.is_empty());
132    Ok(sols.clone())
133}
134
135#[allow(clippy::unwrap_used)]
136pub fn get_solutions_from_conjure(
137    essence_file: &str,
138    param_file: Option<&str>,
139    context: Arc<RwLock<Context<'static>>>,
140) -> Result<Vec<BTreeMap<Name, Literal>>, anyhow::Error> {
141    let tmp_dir = tempdir()?;
142
143    let mut cmd = std::process::Command::new("conjure");
144
145    cmd.arg("solve")
146        .arg("--number-of-solutions=all")
147        .arg("--copy-solutions=no")
148        .arg("-o")
149        .arg(tmp_dir.path())
150        .arg(essence_file);
151
152    if let Some(file) = param_file {
153        cmd.arg(file);
154    }
155
156    let output = cmd.output()?;
157
158    if !output.status.success() {
159        let stderr =
160            String::from_utf8(output.stderr).unwrap_or_else(|e| e.utf8_error().to_string());
161        return Err(anyhow::Error::msg(format!(
162            "Error: `conjure solve` exited with code {}; stderr: {}",
163            output.status, stderr
164        )));
165    }
166
167    let solutions_files: Vec<_> =
168        glob(&format!("{}/*.solution", tmp_dir.path().display()))?.collect();
169
170    let solutions_set: Vec<_> = solutions_files
171        .par_iter()
172        .map(|solutions_file| {
173            let solutions_file = solutions_file.as_ref().unwrap();
174            let model = parse_essence_file(solutions_file.to_str().unwrap(), Arc::clone(&context))
175                .expect("conjure solutions files to be parsable");
176
177            let mut solutions = BTreeMap::new();
178            for (name, decl) in model.symbols().clone().into_iter() {
179                match &decl.kind() as &DeclarationKind {
180                    conjure_cp::ast::DeclarationKind::ValueLetting(expression, _) => {
181                        let literal = expression
182                            .clone()
183                            .into_literal()
184                            .expect("lettings in a solution should only contain literals");
185                        solutions.insert(name, literal);
186                    }
187                    _ => {
188                        bug!("only expect value letting declarations in solutions")
189                    }
190                }
191            }
192            solutions
193        })
194        .collect();
195
196    Ok(solutions_set
197        .into_iter()
198        .filter(|x| !x.is_empty())
199        .collect())
200}
201
202pub fn solutions_to_json(solutions: &Vec<BTreeMap<Name, Literal>>) -> JsonValue {
203    let mut json_solutions = Vec::new();
204    for solution in solutions {
205        let mut json_solution = Map::new();
206        for (var_name, constant) in solution {
207            let serialized_constant = serde_json::to_value(constant).unwrap();
208            json_solution.insert(var_name.to_string(), serialized_constant);
209        }
210        json_solutions.push(JsonValue::Object(json_solution));
211    }
212    let ans = JsonValue::Array(json_solutions);
213    sort_json_object(&ans, true)
214}