1use std::collections::{BTreeMap, HashMap, HashSet};
2use std::fmt::Debug;
3use std::path::Path;
4use std::{io, mem, vec};
5
6use conjure_cp::ast::records::Field;
7use conjure_cp::ast::serde::ObjId;
8use itertools::Itertools as _;
9use std::fs::File;
10use std::hash::Hash;
11use std::io::{BufRead, BufReader};
12use std::sync::{Arc, RwLock};
13use uniplate::Uniplate;
14
15use conjure_cp::ast::pretty::pretty_expression_domain_annotation;
16use conjure_cp::ast::{AbstractLiteral, Expression, GroundDomain, Moo, SerdeModel};
17use conjure_cp::context::Context;
18use serde_json::{Error as JsonError, Value as JsonValue};
19
20use conjure_cp::error::Error;
21
22use crate::utils::conjure::solutions_to_essence;
23use crate::utils::json::sort_json_object;
24use crate::utils::misc::to_set;
25use conjure_cp::Model as ConjureModel;
26use conjure_cp::ast::Name::User;
27use conjure_cp::ast::{Literal, Name};
28use conjure_cp::settings::SolverFamily;
29
30pub const REWRITE_SERIALISED_JSON_MAX_LINES: usize = 1000;
32
33pub const DEFAULT_TEXT_SNAPSHOT_CHARACTER_LIMIT: usize = 1_000_000;
35
36fn model_to_json_with_stable_ids(model: &SerdeModel) -> Result<JsonValue, JsonError> {
41 let id_map = model.collect_stable_id_mapping();
43
44 let mut json = serde_json::to_value(model)?;
46
47 replace_ids(&mut json, &id_map);
49
50 Ok(json)
51}
52
53fn replace_ids(value: &mut JsonValue, id_map: &HashMap<ObjId, ObjId>) {
58 match value {
59 JsonValue::Object(map) => {
60 for (k, v) in map.iter_mut() {
65 if (k == "id" || k == "ptr" || k == "parent")
66 && let Ok(old_id) = serde_json::from_value::<ObjId>(mem::take(v))
67 {
68 let new_id = id_map.get(&old_id).expect("all ids to be in the id map");
69 *v = serde_json::to_value(new_id)
70 .expect("serialization of an ObjId to always succeed");
71 }
72 }
73
74 for val in map.values_mut() {
76 replace_ids(val, id_map);
77 }
78 }
79 JsonValue::Array(arr) => {
80 for item in arr {
81 replace_ids(item, id_map);
82 }
83 }
84 _ => {}
85 }
86}
87
88pub fn assert_eq_any_order<T: Eq + Hash + Debug + Clone>(a: &Vec<Vec<T>>, b: &Vec<Vec<T>>) {
89 assert_eq!(a.len(), b.len());
90
91 let mut a_rows: Vec<HashSet<T>> = Vec::new();
92 for row in a {
93 let hash_row = to_set(row);
94 a_rows.push(hash_row);
95 }
96
97 let mut b_rows: Vec<HashSet<T>> = Vec::new();
98 for row in b {
99 let hash_row = to_set(row);
100 b_rows.push(hash_row);
101 }
102
103 for row in a_rows {
104 assert!(b_rows.contains(&row));
105 }
106}
107
108pub fn serialize_model(model: &ConjureModel) -> Result<String, JsonError> {
109 let serde_model: SerdeModel = model.clone().into();
110
111 let json_with_stable_ids = model_to_json_with_stable_ids(&serde_model)?;
113
114 let sorted_json = sort_json_object(&json_with_stable_ids, false);
116
117 serde_json::to_string_pretty(&sorted_json)
119}
120
121pub fn serialize_domains(model: &ConjureModel) -> Result<String, JsonError> {
122 let mut output = String::new();
123 for constraint in model.constraints() {
124 serialize_domains_expr(constraint, 0, &mut output);
125 }
126 Ok(output)
127}
128
129fn serialize_domains_expr(expr: &Expression, depth: usize, output: &mut String) {
130 let domain = expr
131 .domain_of()
132 .map(|domain| domain.to_string())
133 .unwrap_or_else(|| "<unknown>".to_owned());
134 output.push_str(&" ".repeat(depth));
135 output.push_str(&pretty_expression_domain_annotation(expr, domain));
136 output.push('\n');
137
138 for child in expr.children() {
139 serialize_domains_expr(&child, depth + 1, output);
140 }
141}
142
143pub fn save_model_json(
144 model: &ConjureModel,
145 path: &str,
146 test_name: &str,
147 test_stage: &str,
148 solver: SolverFamily,
149) -> Result<(), std::io::Error> {
150 let marker = solver.as_str();
151 let generated_json_str = serialize_model(model)?;
152 let generated_json_str = maybe_truncate_serialised_json(generated_json_str, test_stage);
153 let filename = format!("{path}/{test_name}-{marker}.generated-{test_stage}.serialised.json");
154 println!("saving: {filename}");
155 std::fs::write(filename, format!("{generated_json_str}\n"))?;
156 Ok(())
157}
158
159pub fn save_stats_json(
160 context: Arc<RwLock<Context<'static>>>,
161 path: &str,
162 test_name: &str,
163 solver: SolverFamily,
164) -> Result<(), std::io::Error> {
165 #[allow(clippy::unwrap_used)]
166 let solver_name = solver.as_str();
167
168 let stats = context.read().unwrap().clone();
169 let generated_json = sort_json_object(&serde_json::to_value(stats)?, false);
170
171 let generated_json_str = serde_json::to_string_pretty(&generated_json)?;
173
174 std::fs::write(
175 format!("{path}/{test_name}-{solver_name}-stats.json"),
176 format!("{generated_json_str}\n"),
177 )?;
178
179 Ok(())
180}
181
182fn read_with_path(path: String) -> Result<String, std::io::Error> {
184 std::fs::read_to_string(&path)
185 .map_err(|e| io::Error::new(e.kind(), format!("{e} (path: {path})")))
186}
187
188pub fn read_model_json(
189 ctx: &Arc<RwLock<Context<'static>>>,
190 path: &str,
191 test_name: &str,
192 prefix: &str,
193 test_stage: &str,
194 solver: SolverFamily,
195) -> Result<ConjureModel, std::io::Error> {
196 let marker = solver.as_str();
197 let filepath = format!("{path}/{test_name}-{marker}.{prefix}-{test_stage}.serialised.json");
198 let expected_json_str = std::fs::read_to_string(filepath)?;
199 let expected_model: SerdeModel = serde_json::from_str(&expected_json_str)?;
200
201 Ok(expected_model.initialise(ctx.clone()).unwrap())
202}
203
204pub fn read_model_json_prefix(
206 path: &str,
207 test_name: &str,
208 prefix: &str,
209 test_stage: &str,
210 solver: SolverFamily,
211 max_lines: usize,
212) -> Result<String, std::io::Error> {
213 let marker = solver.as_str();
214 let filename = format!("{path}/{test_name}-{marker}.{prefix}-{test_stage}.serialised.json");
215 println!("reading: {filename}");
216 read_first_n_lines(filename, max_lines)
217}
218
219pub fn minion_solutions_from_json(
220 serialized: &str,
221) -> Result<Vec<HashMap<Name, Literal>>, anyhow::Error> {
222 let json: JsonValue = serde_json::from_str(serialized)?;
223
224 let json_array = json
225 .as_array()
226 .ok_or(Error::Parse("Invalid JSON".to_owned()))?;
227
228 let mut solutions = Vec::new();
229
230 for solution in json_array {
231 let mut sol = HashMap::new();
232 let solution = solution
233 .as_object()
234 .ok_or(Error::Parse("Invalid JSON".to_owned()))?;
235
236 for (var_name, constant) in solution {
237 let constant = match constant {
238 JsonValue::Number(n) => {
239 let n = n
240 .as_i64()
241 .ok_or(Error::Parse("Invalid integer".to_owned()))?;
242 Literal::Int(n as i32)
243 }
244 JsonValue::Bool(b) => Literal::Bool(*b),
245 _ => return Err(Error::Parse("Invalid constant".to_owned()).into()),
246 };
247
248 sol.insert(User(var_name.into()), constant);
249 }
250
251 solutions.push(sol);
252 }
253
254 Ok(solutions)
255}
256
257pub fn save_solutions_essence(
259 solutions: &[BTreeMap<Name, Literal>],
260 path: &str,
261 test_name: &str,
262 solver: SolverFamily,
263) -> Result<String, std::io::Error> {
264 let rendered = solutions_to_essence(solutions);
265 let solver_name = solver.as_str();
266 let filename = format!("{path}/{test_name}-{solver_name}.generated.solutions");
267 std::fs::write(filename, &rendered)?;
268
269 Ok(rendered)
270}
271
272pub fn read_solutions_essence(
273 path: &str,
274 test_name: &str,
275 prefix: &str,
276 solver: SolverFamily,
277) -> Result<String, anyhow::Error> {
278 let solver_name = solver.as_str();
279 let filename = format!("{path}/{test_name}-{solver_name}.{prefix}.solutions");
280 Ok(read_with_path(filename)?)
281}
282
283pub fn read_default_rule_trace(
285 path: &str,
286 test_name: &str,
287 prefix: &str,
288 solver: &SolverFamily,
289) -> Result<String, std::io::Error> {
290 let solver_name = solver.as_str();
291 let filename = format!("{path}/{test_name}-{solver_name}-{prefix}-rule-trace.txt");
292 Ok(truncate_to_first_chars(
293 &read_with_path(filename)?,
294 DEFAULT_TEXT_SNAPSHOT_CHARACTER_LIMIT,
295 ))
296}
297
298#[doc(hidden)]
299pub fn normalize_solutions_for_comparison(
300 input_solutions: &[BTreeMap<Name, Literal>],
301) -> Vec<BTreeMap<Name, Literal>> {
302 let mut normalized = input_solutions.to_vec();
303
304 for solset in &mut normalized {
305 let keys_to_remove: Vec<Name> = solset
307 .keys()
308 .filter(|k| matches!(k, Name::Machine(_)))
309 .cloned()
310 .collect();
311 for k in keys_to_remove {
312 solset.remove(&k);
313 }
314
315 let mut updates = vec![];
316 for (k, v) in solset.clone() {
317 if let Name::User(_) = k {
318 match v {
319 Literal::Bool(true) => updates.push((k, Literal::Int(1))),
320 Literal::Bool(false) => updates.push((k, Literal::Int(0))),
321 Literal::Int(_) | Literal::EnumVariant(_) | Literal::Unnamed(_) => {}
322 Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, _)) => {
323 let mut matrix = AbstractLiteral::Matrix(
327 elems,
328 Moo::new(GroundDomain::Int(vec![], None)),
329 );
330 matrix = matrix.transform(&move |x: AbstractLiteral<Literal>| match x {
331 AbstractLiteral::Matrix(items, _) => {
332 let items = items
333 .into_iter()
334 .map(|x| match x {
335 Literal::Bool(false) => Literal::Int(0),
336 Literal::Bool(true) => Literal::Int(1),
337 x => x,
338 })
339 .collect_vec();
340
341 AbstractLiteral::Matrix(
342 items,
343 Moo::new(GroundDomain::Int(vec![], None)),
344 )
345 }
346 x => x,
347 });
348 updates.push((k, Literal::AbstractLiteral(matrix)));
349 }
350 Literal::AbstractLiteral(AbstractLiteral::Tuple(elems)) => {
351 let mut tuple = AbstractLiteral::Tuple(elems);
354 tuple = tuple.transform(
355 &(move |x: AbstractLiteral<Literal>| match x {
356 AbstractLiteral::Tuple(items) => {
357 let items = items
358 .into_iter()
359 .map(|x| match x {
360 Literal::Bool(false) => Literal::Int(0),
361 Literal::Bool(true) => Literal::Int(1),
362 x => x,
363 })
364 .collect_vec();
365
366 AbstractLiteral::Tuple(items)
367 }
368 x => x,
369 }),
370 );
371 updates.push((k, Literal::AbstractLiteral(tuple)));
372 }
373 Literal::AbstractLiteral(AbstractLiteral::Record(entries)) => {
374 let mut record = AbstractLiteral::Record(entries);
377 record = record.transform(&move |x: AbstractLiteral<Literal>| match x {
378 AbstractLiteral::Record(entries) => {
379 let entries = entries
380 .into_iter()
381 .map(|x| {
382 let Field { name, value } = x;
383 {
384 let value = match value {
385 Literal::Bool(false) => Literal::Int(0),
386 Literal::Bool(true) => Literal::Int(1),
387 x => x,
388 };
389 Field { name, value }
390 }
391 })
392 .collect_vec();
393
394 AbstractLiteral::Record(entries)
395 }
396 x => x,
397 });
398 updates.push((k, Literal::AbstractLiteral(record)));
399 }
400 Literal::AbstractLiteral(AbstractLiteral::Variant(entry)) => {
401 let mut variant = AbstractLiteral::Variant(entry);
402 variant = variant.transform(&move |x| match x {
403 AbstractLiteral::Variant(entry) => {
404 let Field { name, value } = Moo::unwrap_or_clone(entry);
405 let value = match value {
406 Literal::Bool(false) => Literal::Int(0),
407 Literal::Bool(true) => Literal::Int(1),
408 value => value,
409 };
410 AbstractLiteral::Variant(Moo::new(Field { name, value }))
411 }
412 value => value,
413 });
414 updates.push((k, Literal::AbstractLiteral(variant)));
415 }
416 Literal::AbstractLiteral(AbstractLiteral::Set(members)) => {
417 let set = AbstractLiteral::Set(members).transform(&move |x| match x {
418 AbstractLiteral::Set(members) => {
419 let members = members
420 .into_iter()
421 .map(|x| match x {
422 Literal::Bool(false) => Literal::Int(0),
423 Literal::Bool(true) => Literal::Int(1),
424 x => x,
425 })
426 .collect_vec();
427
428 AbstractLiteral::Set(members)
429 }
430 x => x,
431 });
432 updates.push((k, Literal::AbstractLiteral(set)));
433 }
434 Literal::AbstractLiteral(AbstractLiteral::MSet(members)) => {
435 let mset = AbstractLiteral::MSet(members).transform(&move |x| match x {
436 AbstractLiteral::MSet(members) => {
437 let members = members
438 .into_iter()
439 .map(|x| match x {
440 Literal::Bool(false) => Literal::Int(0),
441 Literal::Bool(true) => Literal::Int(1),
442 x => x,
443 })
444 .collect_vec();
445 AbstractLiteral::MSet(members)
446 }
447 x => x,
448 });
449 updates.push((k, Literal::AbstractLiteral(mset)));
450 }
451 Literal::AbstractLiteral(AbstractLiteral::Sequence(elems)) => {
452 let sequence =
453 AbstractLiteral::Sequence(elems).transform(&move |x| match x {
454 AbstractLiteral::Sequence(elems) => {
455 let elems = elems
456 .into_iter()
457 .map(|x| match x {
458 Literal::Bool(false) => Literal::Int(0),
459 Literal::Bool(true) => Literal::Int(1),
460 x => x,
461 })
462 .collect_vec();
463 AbstractLiteral::Sequence(elems)
464 }
465 x => x,
466 });
467 updates.push((k, Literal::AbstractLiteral(sequence)));
468 }
469 Literal::AbstractLiteral(AbstractLiteral::Function(pairs)) => {
470 let function =
471 AbstractLiteral::Function(pairs).transform(&move |x| match x {
472 AbstractLiteral::Function(pairs) => {
473 let pairs = pairs
474 .into_iter()
475 .map(|(key, value)| {
476 let normalize = |x| match x {
477 Literal::Bool(false) => Literal::Int(0),
478 Literal::Bool(true) => Literal::Int(1),
479 x => x,
480 };
481 (normalize(key), normalize(value))
482 })
483 .collect_vec();
484 AbstractLiteral::Function(pairs)
485 }
486 x => x,
487 });
488 updates.push((k, Literal::AbstractLiteral(function)));
489 }
490 Literal::AbstractLiteral(AbstractLiteral::Relation(tuples)) => {
491 let relation =
492 AbstractLiteral::Relation(tuples).transform(&move |x| match x {
493 AbstractLiteral::Relation(tuples) => {
494 let tuples = tuples
495 .into_iter()
496 .map(|fields| {
497 fields
498 .into_iter()
499 .map(|x| match x {
500 Literal::Bool(false) => Literal::Int(0),
501 Literal::Bool(true) => Literal::Int(1),
502 x => x,
503 })
504 .collect_vec()
505 })
506 .collect_vec();
507 AbstractLiteral::Relation(tuples)
508 }
509 x => x,
510 });
511 updates.push((k, Literal::AbstractLiteral(relation)));
512 }
513 Literal::AbstractLiteral(AbstractLiteral::Partition(parts)) => {
514 let partition =
515 AbstractLiteral::Partition(parts).transform(&move |x| match x {
516 AbstractLiteral::Partition(parts) => {
517 let parts = parts
518 .into_iter()
519 .map(|part| {
520 part.into_iter()
521 .map(|x| match x {
522 Literal::Bool(false) => Literal::Int(0),
523 Literal::Bool(true) => Literal::Int(1),
524 x => x,
525 })
526 .collect_vec()
527 })
528 .collect_vec();
529 AbstractLiteral::Partition(parts)
530 }
531 x => x,
532 });
533 updates.push((k, Literal::AbstractLiteral(partition)));
534 }
535 Literal::AbstractLiteral(AbstractLiteral::Permutation(cycles)) => {
536 let permutation =
537 AbstractLiteral::Permutation(cycles).transform(&move |x| match x {
538 AbstractLiteral::Permutation(cycles) => {
539 let cycles = cycles
540 .into_iter()
541 .map(|cycle| {
542 cycle
543 .into_iter()
544 .map(|x| match x {
545 Literal::Bool(false) => Literal::Int(0),
546 Literal::Bool(true) => Literal::Int(1),
547 x => x,
548 })
549 .collect_vec()
550 })
551 .collect_vec();
552 AbstractLiteral::Permutation(cycles)
553 }
554 x => x,
555 });
556 updates.push((k, Literal::AbstractLiteral(permutation)));
557 }
558 }
559 }
560 }
561
562 for (k, v) in updates {
563 let v = match v {
564 Literal::AbstractLiteral(value) => {
565 Literal::AbstractLiteral(normalize_set_literal_order(value))
566 }
567 value => value,
568 };
569 solset.insert(k, v);
570 }
571 }
572
573 normalized = normalized.into_iter().unique().collect();
575 normalized.sort_by(solution_essence_cmp);
576 normalized
577}
578
579fn solution_essence_cmp(
580 lhs: &BTreeMap<Name, Literal>,
581 rhs: &BTreeMap<Name, Literal>,
582) -> std::cmp::Ordering {
583 lhs.iter()
584 .zip(rhs)
585 .find_map(|((lhs_name, lhs_value), (rhs_name, rhs_value))| {
586 let ordering = lhs_name.cmp(rhs_name);
587 (ordering != std::cmp::Ordering::Equal)
588 .then_some(ordering)
589 .or_else(|| {
590 let ordering = lhs_value.essence_cmp(rhs_value);
591 (ordering != std::cmp::Ordering::Equal).then_some(ordering)
592 })
593 })
594 .unwrap_or_else(|| lhs.len().cmp(&rhs.len()))
595}
596
597fn normalize_set_literal_order(value: AbstractLiteral<Literal>) -> AbstractLiteral<Literal> {
598 value.transform(&|value| match value {
599 AbstractLiteral::Set(mut members) => {
600 members.sort_by(Literal::essence_cmp);
601 AbstractLiteral::Set(members)
602 }
603 AbstractLiteral::MSet(mut members) => {
604 members.sort_by(Literal::essence_cmp);
605 AbstractLiteral::MSet(members)
606 }
607 AbstractLiteral::Function(mut pairs) => {
608 pairs.sort_by(|(k1, _), (k2, _)| Literal::essence_cmp(k1, k2));
609 AbstractLiteral::Function(pairs)
610 }
611 AbstractLiteral::Relation(mut tuples) => {
612 tuples.sort_by(|a, b| {
613 a.iter()
614 .zip(b.iter())
615 .map(|(x, y)| Literal::essence_cmp(x, y))
616 .find(|ord| *ord != std::cmp::Ordering::Equal)
617 .unwrap_or(std::cmp::Ordering::Equal)
618 });
619 AbstractLiteral::Relation(tuples)
620 }
621 AbstractLiteral::Partition(mut parts) => {
622 for part in parts.iter_mut() {
623 part.sort_by(Literal::essence_cmp);
624 }
625 parts.sort_by(|a, b| {
626 a.iter()
627 .zip(b.iter())
628 .map(|(x, y)| Literal::essence_cmp(x, y))
629 .find(|ord| *ord != std::cmp::Ordering::Equal)
630 .unwrap_or_else(|| a.len().cmp(&b.len()))
631 });
632 AbstractLiteral::Partition(parts)
633 }
634 value => value,
635 })
636}
637
638fn maybe_truncate_serialised_json(serialised: String, test_stage: &str) -> String {
639 if test_stage == "rewrite" {
640 truncate_to_first_lines(&serialised, REWRITE_SERIALISED_JSON_MAX_LINES)
641 } else {
642 serialised
643 }
644}
645
646fn truncate_to_first_lines(content: &str, max_lines: usize) -> String {
647 content.lines().take(max_lines).join("\n")
648}
649
650pub fn truncate_to_first_chars(content: &str, max_chars: usize) -> String {
651 match content.char_indices().nth(max_chars) {
652 Some((idx, _)) => content[..idx].to_owned(),
653 None => content.to_owned(),
654 }
655}
656
657fn read_first_n_lines<P: AsRef<Path>>(filename: P, n: usize) -> io::Result<String> {
658 let reader = BufReader::new(File::open(&filename)?);
659 let lines = reader
660 .lines()
661 .chunks(n)
662 .into_iter()
663 .next()
664 .unwrap()
665 .collect::<Result<Vec<_>, _>>()?;
666 Ok(lines.join("\n"))
667}
668
669#[cfg(test)]
670mod tests {
671 use super::*;
672
673 fn set(members: Vec<Literal>) -> Literal {
674 Literal::AbstractLiteral(AbstractLiteral::Set(members))
675 }
676
677 #[test]
678 fn solution_normalization_sorts_nested_set_members_by_essence_order() {
679 let inner_one_two = set(vec![Literal::Int(2), Literal::Int(1)]);
680 let inner_two = set(vec![Literal::Int(2)]);
681 let mut oxide_solution = BTreeMap::new();
682 oxide_solution.insert(
683 Name::User("x".into()),
684 set(vec![inner_two.clone(), inner_one_two.clone()]),
685 );
686 let mut conjure_solution = BTreeMap::new();
687 conjure_solution.insert(
688 Name::User("x".into()),
689 set(vec![inner_one_two, inner_two.clone()]),
690 );
691
692 let normalized_oxide = normalize_solutions_for_comparison(&[oxide_solution]);
693 let normalized_conjure = normalize_solutions_for_comparison(&[conjure_solution]);
694 let expected = set(vec![inner_two, set(vec![Literal::Int(1), Literal::Int(2)])]);
695
696 assert_eq!(normalized_oxide, normalized_conjure);
697 assert_eq!(
698 normalized_oxide[0].get(&Name::User("x".into())),
699 Some(&expected)
700 );
701 }
702}