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.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 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}