conjure_oxide/utils/
conjure.rs1use std::collections::BTreeMap;
2use std::path::PathBuf;
3use std::rc::Rc;
4use std::string::ToString;
5use std::sync::{Arc, Mutex, RwLock};
6
7use conjure_core::ast::{Literal, Name};
8use conjure_core::bug;
9use conjure_core::context::Context;
10
11use conjure_core::solver::adaptors::Sat;
12use serde_json::{Map, Value as JsonValue};
13
14use itertools::Itertools as _;
15use tempfile::tempdir;
16
17use crate::parse_essence_file;
18use crate::solver::adaptors::Minion;
19use crate::solver::Solver;
20use crate::utils::json::sort_json_object;
21use crate::EssenceParseError;
22use crate::Model;
23
24use glob::glob;
25
26pub fn get_minion_solutions(
27 model: Model,
28 num_sols: i32,
29 solver_input_file: &Option<PathBuf>,
30) -> Result<Vec<BTreeMap<Name, Literal>>, anyhow::Error> {
31 let solver = Solver::new(Minion::new());
32 eprintln!("Building Minion model...");
33
34 let symbols_rc = Rc::clone(model.as_submodel().symbols_ptr_unchecked());
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 mut file = std::fs::File::create(solver_input_file)?;
45 solver.write_solver_input_file(&mut file)?;
46 }
47
48 eprintln!("Running Minion...");
49
50 let all_solutions_ref = Arc::new(Mutex::<Vec<BTreeMap<Name, Literal>>>::new(vec![]));
51 let all_solutions_ref_2 = all_solutions_ref.clone();
52 let solver = if num_sols > 0 {
53 let sols_left = Mutex::new(num_sols);
54
55 #[allow(clippy::unwrap_used)]
56 solver
57 .solve(Box::new(move |sols| {
58 let mut all_solutions = (*all_solutions_ref_2).lock().unwrap();
59 (*all_solutions).push(sols.into_iter().collect());
60 let mut sols_left = sols_left.lock().unwrap();
61 *sols_left -= 1;
62
63 *sols_left != 0
64 }))
65 .unwrap()
66 } else {
67 #[allow(clippy::unwrap_used)]
68 solver
69 .solve(Box::new(move |sols| {
70 let mut all_solutions = (*all_solutions_ref_2).lock().unwrap();
71 (*all_solutions).push(sols.into_iter().collect());
72 true
73 }))
74 .unwrap()
75 };
76
77 solver.save_stats_to_context();
78
79 #[allow(clippy::unwrap_used)]
80 let mut sols_guard = (*all_solutions_ref).lock().unwrap();
81 let sols = &mut *sols_guard;
82 let symbols = symbols_rc.borrow();
83
84 let names = symbols.clone().into_iter().map(|x| x.0).collect_vec();
85 let representations = names
86 .into_iter()
87 .filter_map(|x| symbols.representations_for(&x).map(|repr| (x, repr)))
88 .filter_map(|(name, reprs)| {
89 if reprs.is_empty() {
90 return None;
91 }
92 assert!(
93 reprs.len() <= 1,
94 "multiple representations for a variable is not yet implemented"
95 );
96
97 assert_eq!(
98 reprs[0].len(),
99 1,
100 "nested representations are not yet implemented"
101 );
102 Some((name, reprs[0][0].clone()))
103 })
104 .collect_vec();
105
106 for sol in sols.iter_mut() {
107 for (name, representation) in representations.iter() {
108 let value = representation.value_up(sol).unwrap();
109 sol.insert(name.clone(), value);
110 }
111
112 *sol = sol
114 .clone()
115 .into_iter()
116 .filter(|(name, _)| !matches!(name, Name::Represented(_)))
117 .collect();
118 }
119
120 Ok(sols.clone().into_iter().filter(|x| !x.is_empty()).collect())
121}
122
123pub fn get_sat_solutions(
124 model: Model,
125 num_sols: i32,
126 solver_input_file: &Option<PathBuf>,
127) -> Result<Vec<BTreeMap<Name, Literal>>, anyhow::Error> {
128 let solver = Solver::new(Sat::default());
129 eprintln!("Building SAT model...");
130 let solver = solver.load_model(model)?;
131
132 if let Some(solver_input_file) = solver_input_file {
133 eprintln!(
134 "Writing solver input file to {}",
135 solver_input_file.display()
136 );
137 let mut file = std::fs::File::create(solver_input_file)?;
138 solver.write_solver_input_file(&mut file)?;
139 }
140
141 eprintln!("Running SAT...");
142
143 let all_solutions_ref = Arc::new(Mutex::<Vec<BTreeMap<Name, Literal>>>::new(vec![]));
144 let all_solutions_ref_2 = all_solutions_ref.clone();
145 let solver = if num_sols > 0 {
146 let sols_left = Mutex::new(num_sols);
147
148 #[allow(clippy::unwrap_used)]
149 solver
150 .solve(Box::new(move |sols| {
151 let mut all_solutions = (*all_solutions_ref_2).lock().unwrap();
152 (*all_solutions).push(sols.into_iter().collect());
153 let mut sols_left = sols_left.lock().unwrap();
154 *sols_left -= 1;
155
156 *sols_left != 0
157 }))
158 .unwrap()
159 } else {
160 #[allow(clippy::unwrap_used)]
161 solver
162 .solve(Box::new(move |sols| {
163 let mut all_solutions = (*all_solutions_ref_2).lock().unwrap();
164 (*all_solutions).push(sols.into_iter().collect());
165 true
166 }))
167 .unwrap()
168 };
169
170 solver.save_stats_to_context();
171
172 #[allow(clippy::unwrap_used)]
173 let sols = (*all_solutions_ref).lock().unwrap();
174
175 Ok((*sols).clone())
176}
177
178#[allow(clippy::unwrap_used)]
179pub fn get_solutions_from_conjure(
180 essence_file: &str,
181 context: Arc<RwLock<Context<'static>>>,
182) -> Result<Vec<BTreeMap<Name, Literal>>, EssenceParseError> {
183 let tmp_dir = tempdir().unwrap();
184
185 let mut cmd = std::process::Command::new("conjure");
186 let output = cmd
187 .arg("solve")
188 .arg("--number-of-solutions=all")
189 .arg("--copy-solutions=no")
190 .arg("-o")
191 .arg(tmp_dir.path())
192 .arg(essence_file)
193 .output()
194 .map_err(|e| EssenceParseError::ConjureSolveError(e.to_string()))?;
195
196 if !output.status.success() {
197 return Err(EssenceParseError::ConjureSolveError(format!(
198 "conjure solve exited with failure: {}",
199 String::from_utf8(output.stderr).unwrap()
200 )));
201 }
202
203 let solutions_files: Vec<_> = glob(&format!("{}/*.solution", tmp_dir.path().display()))
204 .unwrap()
205 .collect();
206
207 let mut solutions_set = vec![];
208 for solutions_file in solutions_files {
209 let solutions_file = solutions_file.unwrap();
210 let model = parse_essence_file(solutions_file.to_str().unwrap(), Arc::clone(&context))
211 .expect("conjure solutions files to be parsable");
212
213 let mut solutions = BTreeMap::new();
214 for (name, decl) in model.as_submodel().symbols().clone().into_iter() {
215 match decl.kind() {
216 conjure_core::ast::DeclarationKind::ValueLetting(expression) => {
217 let literal = expression
218 .clone()
219 .into_literal()
220 .expect("lettings in a solution should only contain literals");
221 solutions.insert(name, literal);
222 }
223 _ => {
224 bug!("only expect value letting declarations in solutions")
225 }
226 }
227 }
228 solutions_set.push(solutions);
229 }
230
231 Ok(solutions_set
232 .into_iter()
233 .filter(|x| !x.is_empty())
234 .collect())
235}
236
237pub fn solutions_to_json(solutions: &Vec<BTreeMap<Name, Literal>>) -> JsonValue {
238 let mut json_solutions = Vec::new();
239 for solution in solutions {
240 let mut json_solution = Map::new();
241 for (var_name, constant) in solution {
242 let serialized_constant = serde_json::to_value(constant).unwrap();
243 json_solution.insert(var_name.to_string(), serialized_constant);
244 }
245 json_solutions.push(JsonValue::Object(json_solution));
246 }
247 let ans = JsonValue::Array(json_solutions);
248 sort_json_object(&ans, true)
249}