conjure_cp_cli/utils/
conjure.rs1use 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 let symbols_ptr = model.as_submodel().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 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 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 #[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 #[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 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 for (name, representation) in representations.iter() {
116 let value = representation.value_up(sol).unwrap();
117 sol.insert(name.clone(), value);
118 }
119
120 *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 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.as_submodel().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
196pub fn solutions_to_json(solutions: &Vec<BTreeMap<Name, Literal>>) -> JsonValue {
197 let mut json_solutions = Vec::new();
198 for solution in solutions {
199 let mut json_solution = Map::new();
200 for (var_name, constant) in solution {
201 let serialized_constant = serde_json::to_value(constant).unwrap();
202 json_solution.insert(var_name.to_string(), serialized_constant);
203 }
204 json_solutions.push(JsonValue::Object(json_solution));
205 }
206 let ans = JsonValue::Array(json_solutions);
207 sort_json_object(&ans, true)
208}