1use std::collections::{BTreeMap, HashMap, HashSet};
2use std::fmt::Debug;
3use std::vec;
4
5use conjure_cp_core::ast::records::RecordValue;
6use conjure_cp_core::bug;
7use itertools::Itertools as _;
8use std::fs::File;
9use std::fs::{OpenOptions, read_to_string};
10use std::hash::Hash;
11use std::io::Write;
12use std::sync::{Arc, RwLock};
13use uniplate::Uniplate;
14
15use conjure_cp_core::ast::{AbstractLiteral, Domain, SerdeModel};
16use conjure_cp_core::context::Context;
17use serde_json::{Error as JsonError, Value as JsonValue, json};
18
19use conjure_cp_core::error::Error;
20
21use crate::Model as ConjureModel;
22use crate::SolverFamily;
23use crate::ast::Name::User;
24use crate::ast::{Literal, Name};
25use crate::utils::conjure::solutions_to_json;
26use crate::utils::json::sort_json_object;
27use crate::utils::misc::to_set;
28
29pub fn assert_eq_any_order<T: Eq + Hash + Debug + Clone>(a: &Vec<Vec<T>>, b: &Vec<Vec<T>>) {
30 assert_eq!(a.len(), b.len());
31
32 let mut a_rows: Vec<HashSet<T>> = Vec::new();
33 for row in a {
34 let hash_row = to_set(row);
35 a_rows.push(hash_row);
36 }
37
38 let mut b_rows: Vec<HashSet<T>> = Vec::new();
39 for row in b {
40 let hash_row = to_set(row);
41 b_rows.push(hash_row);
42 }
43
44 println!("{a_rows:?},{b_rows:?}");
45 for row in a_rows {
46 assert!(b_rows.contains(&row));
47 }
48}
49
50pub fn serialise_model(model: &ConjureModel) -> Result<String, JsonError> {
51 let serde_model: SerdeModel = model.clone().into();
55 let generated_json = sort_json_object(&serde_json::to_value(serde_model)?, false);
56
57 let generated_json_str = serde_json::to_string_pretty(&generated_json)?;
59
60 Ok(generated_json_str)
61}
62
63pub fn save_model_json(
64 model: &ConjureModel,
65 path: &str,
66 test_name: &str,
67 test_stage: &str,
68) -> Result<(), std::io::Error> {
69 let generated_json_str = serialise_model(model)?;
70 let filename = format!("{path}/{test_name}.generated-{test_stage}.serialised.json");
71 File::create(&filename)?.write_all(generated_json_str.as_bytes())?;
72 Ok(())
73}
74
75pub fn save_stats_json(
76 context: Arc<RwLock<Context<'static>>>,
77 path: &str,
78 test_name: &str,
79) -> Result<(), std::io::Error> {
80 #[allow(clippy::unwrap_used)]
81 let stats = context.read().unwrap().clone();
82 let generated_json = sort_json_object(&serde_json::to_value(stats)?, false);
83
84 let generated_json_str = serde_json::to_string_pretty(&generated_json)?;
86
87 File::create(format!("{path}/{test_name}-stats.json"))?
88 .write_all(generated_json_str.as_bytes())?;
89
90 Ok(())
91}
92
93pub fn read_model_json(
94 ctx: &Arc<RwLock<Context<'static>>>,
95 path: &str,
96 test_name: &str,
97 prefix: &str,
98 test_stage: &str,
99) -> Result<ConjureModel, std::io::Error> {
100 let expected_json_str = std::fs::read_to_string(format!(
101 "{path}/{test_name}.{prefix}-{test_stage}.serialised.json"
102 ))?;
103 println!("{path}/{test_name}.{prefix}-{test_stage}.serialised.json");
104 let expected_model: SerdeModel = serde_json::from_str(&expected_json_str)?;
105
106 Ok(expected_model.initialise(ctx.clone()).unwrap())
107}
108
109pub fn minion_solutions_from_json(
110 serialized: &str,
111) -> Result<Vec<HashMap<Name, Literal>>, anyhow::Error> {
112 let json: JsonValue = serde_json::from_str(serialized)?;
113
114 let json_array = json
115 .as_array()
116 .ok_or(Error::Parse("Invalid JSON".to_owned()))?;
117
118 let mut solutions = Vec::new();
119
120 for solution in json_array {
121 let mut sol = HashMap::new();
122 let solution = solution
123 .as_object()
124 .ok_or(Error::Parse("Invalid JSON".to_owned()))?;
125
126 for (var_name, constant) in solution {
127 let constant = match constant {
128 JsonValue::Number(n) => {
129 let n = n
130 .as_i64()
131 .ok_or(Error::Parse("Invalid integer".to_owned()))?;
132 Literal::Int(n as i32)
133 }
134 JsonValue::Bool(b) => Literal::Bool(*b),
135 _ => return Err(Error::Parse("Invalid constant".to_owned()).into()),
136 };
137
138 sol.insert(User(var_name.into()), constant);
139 }
140
141 solutions.push(sol);
142 }
143
144 Ok(solutions)
145}
146
147pub fn save_solutions_json(
149 solutions: &Vec<BTreeMap<Name, Literal>>,
150 path: &str,
151 test_name: &str,
152 solver: SolverFamily,
153) -> Result<JsonValue, std::io::Error> {
154 let json_solutions = solutions_to_json(solutions);
155 let generated_json_str = serde_json::to_string_pretty(&json_solutions)?;
156
157 let solver_name = match solver {
158 SolverFamily::Sat => "sat",
159 SolverFamily::Minion => "minion",
160 };
161
162 let filename = format!("{path}/{test_name}.generated-{solver_name}.solutions.json");
163 File::create(&filename)?.write_all(generated_json_str.as_bytes())?;
164
165 Ok(json_solutions)
166}
167
168pub fn read_solutions_json(
169 path: &str,
170 test_name: &str,
171 prefix: &str,
172 solver: SolverFamily,
173) -> Result<JsonValue, anyhow::Error> {
174 let solver_name = match solver {
175 SolverFamily::Sat => "sat",
176 SolverFamily::Minion => "minion",
177 };
178
179 let expected_json_str = std::fs::read_to_string(format!(
180 "{path}/{test_name}.{prefix}-{solver_name}.solutions.json"
181 ))?;
182
183 let expected_solutions: JsonValue =
184 sort_json_object(&serde_json::from_str(&expected_json_str)?, true);
185
186 Ok(expected_solutions)
187}
188
189pub fn read_rule_trace(
192 path: &str,
193 test_name: &str,
194 prefix: &str,
195) -> Result<Vec<String>, std::io::Error> {
196 let filename = format!("{path}/{test_name}-{prefix}-rule-trace.json");
197 let mut rules_trace: Vec<String> = read_to_string(&filename)?
198 .lines()
199 .map(String::from)
200 .collect();
201
202 if prefix == "generated" {
204 let rule_count = rules_trace.len();
205 let count_message = json!({
206 "message": "Number of rules applied",
207 "count": rule_count
208 });
209 let count_message_string = serde_json::to_string(&count_message)?;
210 rules_trace.push(count_message_string);
211
212 let mut file = OpenOptions::new()
214 .write(true)
215 .truncate(true)
216 .open(&filename)?;
217 writeln!(file, "{}", rules_trace.join("\n"))?;
218 }
219
220 Ok(rules_trace)
221}
222
223pub fn read_human_rule_trace(
225 path: &str,
226 test_name: &str,
227 prefix: &str,
228) -> Result<Vec<String>, std::io::Error> {
229 let filename = format!("{path}/{test_name}-{prefix}-rule-trace-human.txt");
230 let rules_trace: Vec<String> = read_to_string(&filename)?
231 .lines()
232 .map(String::from)
233 .collect();
234
235 Ok(rules_trace)
236}
237
238#[doc(hidden)]
239pub fn normalize_solutions_for_comparison(
240 input_solutions: &[BTreeMap<Name, Literal>],
241) -> Vec<BTreeMap<Name, Literal>> {
242 let mut normalized = input_solutions.to_vec();
243
244 for solset in &mut normalized {
245 let keys_to_remove: Vec<Name> = solset
247 .keys()
248 .filter(|k| matches!(k, Name::Machine(_)))
249 .cloned()
250 .collect();
251 for k in keys_to_remove {
252 solset.remove(&k);
253 }
254
255 let mut updates = vec![];
256 for (k, v) in solset.clone() {
257 if let Name::User(_) = k {
258 match v {
259 Literal::Bool(true) => updates.push((k, Literal::Int(1))),
260 Literal::Bool(false) => updates.push((k, Literal::Int(0))),
261 Literal::Int(_) => {}
262 Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, _)) => {
263 let mut matrix =
267 AbstractLiteral::Matrix(elems, Box::new(Domain::Int(vec![])));
268 matrix = matrix.transform(&move |x: AbstractLiteral<Literal>| match x {
269 AbstractLiteral::Matrix(items, _) => {
270 let items = items
271 .into_iter()
272 .map(|x| match x {
273 Literal::Bool(false) => Literal::Int(0),
274 Literal::Bool(true) => Literal::Int(1),
275 x => x,
276 })
277 .collect_vec();
278
279 AbstractLiteral::Matrix(items, Box::new(Domain::Int(vec![])))
280 }
281 x => x,
282 });
283 updates.push((k, Literal::AbstractLiteral(matrix)));
284 }
285 Literal::AbstractLiteral(AbstractLiteral::Tuple(elems)) => {
286 let mut tuple = AbstractLiteral::Tuple(elems);
289 tuple = tuple.transform(
290 &(move |x: AbstractLiteral<Literal>| match x {
291 AbstractLiteral::Tuple(items) => {
292 let items = items
293 .into_iter()
294 .map(|x| match x {
295 Literal::Bool(false) => Literal::Int(0),
296 Literal::Bool(true) => Literal::Int(1),
297 x => x,
298 })
299 .collect_vec();
300
301 AbstractLiteral::Tuple(items)
302 }
303 x => x,
304 }),
305 );
306 updates.push((k, Literal::AbstractLiteral(tuple)));
307 }
308 Literal::AbstractLiteral(AbstractLiteral::Record(entries)) => {
309 let mut record = AbstractLiteral::Record(entries);
312 record = record.transform(&move |x: AbstractLiteral<Literal>| match x {
313 AbstractLiteral::Record(entries) => {
314 let entries = entries
315 .into_iter()
316 .map(|x| {
317 let RecordValue { name, value } = x;
318 {
319 let value = match value {
320 Literal::Bool(false) => Literal::Int(0),
321 Literal::Bool(true) => Literal::Int(1),
322 x => x,
323 };
324 RecordValue { name, value }
325 }
326 })
327 .collect_vec();
328
329 AbstractLiteral::Record(entries)
330 }
331 x => x,
332 });
333 updates.push((k, Literal::AbstractLiteral(record)));
334 }
335 e => bug!("unexpected literal type: {e:?}"),
336 }
337 }
338 }
339
340 for (k, v) in updates {
341 solset.insert(k, v);
342 }
343 }
344
345 normalized = normalized.into_iter().unique().collect();
347 normalized
348}