use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::path::Path;
use std::sync::Arc;
use anyhow::{Context, Result};
use rustsat::instances::{BasicVarManager, Cnf, SatInstance};
use rustsat::types::{Clause, Lit};
use serde::{Deserialize, Serialize};
use crate::problem::{PuzLit, PuzVar};
use super::parse::{EPrimeAnnotations, PuzzleParse};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableEPrimeAnnotations {
pub vars: BTreeSet<String>,
pub auxvars: BTreeSet<String>,
pub cons: BTreeMap<String, String>,
pub reveal: BTreeMap<String, String>,
pub reveal_values: BTreeSet<String>,
pub params: BTreeMap<String, serde_json::Value>,
pub kind: Option<String>,
pub info: Vec<String>,
#[serde(default)]
pub decs: Vec<String>,
}
impl From<&EPrimeAnnotations> for SerializableEPrimeAnnotations {
fn from(e: &EPrimeAnnotations) -> Self {
Self {
vars: e.vars.clone(),
auxvars: e.auxvars.clone(),
cons: e.cons.clone(),
reveal: e.reveal.clone(),
reveal_values: e.reveal_values.clone(),
params: e.params().clone(),
kind: e.kind.clone(),
info: e.info.clone(),
decs: e.decs.clone(),
}
}
}
impl From<SerializableEPrimeAnnotations> for EPrimeAnnotations {
fn from(s: SerializableEPrimeAnnotations) -> Self {
Self {
vars: s.vars,
auxvars: s.auxvars,
cons: s.cons,
reveal: s.reveal.clone(),
reveal_values: s.reveal_values,
params: s.params,
kind: s.kind,
info: s.info,
decs: s.decs,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializablePuzzleParse {
pub eprime: SerializableEPrimeAnnotations,
pub cnf_clauses: Vec<Vec<i32>>,
pub litmap: Vec<(PuzLit, i32)>,
pub invlitmap: BTreeMap<String, BTreeSet<PuzLit>>,
pub domainmap: Vec<(PuzVar, BTreeSet<i64>)>,
pub conset: BTreeMap<String, String>,
pub invconset: BTreeMap<String, i32>,
pub varlits_in_con: BTreeMap<String, Vec<i32>>,
pub varset_lits: BTreeSet<i32>,
pub varset_lits_neg: BTreeSet<i32>,
pub conset_lits: BTreeSet<i32>,
pub order_encoding_map: Vec<(PuzVar, HashSet<i32>)>,
pub inv_order_encoding_map: BTreeMap<String, PuzVar>,
pub order_encoding_all_lits: BTreeSet<i32>,
pub reveal_map: BTreeMap<String, i32>,
}
fn lit_to_i32(lit: Lit) -> i32 {
lit.to_ipasir()
}
fn i32_to_lit(i: i32) -> Result<Lit> {
Lit::from_ipasir(i).context("Invalid literal value")
}
impl TryFrom<&PuzzleParse> for SerializablePuzzleParse {
type Error = anyhow::Error;
fn try_from(p: &PuzzleParse) -> Result<Self> {
let cnf_clauses: Vec<Vec<i32>> = p
.cnf
.as_ref()
.map(|cnf| {
cnf.iter()
.map(|clause| clause.iter().map(|&lit| lit_to_i32(lit)).collect())
.collect()
})
.unwrap_or_default();
Ok(Self {
eprime: SerializableEPrimeAnnotations::from(&p.eprime),
cnf_clauses,
litmap: p
.litmap
.iter()
.map(|(k, &v)| (k.clone(), lit_to_i32(v)))
.collect(),
invlitmap: p
.invlitmap
.iter()
.map(|(&k, v)| (lit_to_i32(k).to_string(), v.clone()))
.collect(),
domainmap: p
.domainmap
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
conset: p
.conset
.iter()
.map(|(&k, v)| (lit_to_i32(k).to_string(), v.clone()))
.collect(),
invconset: p
.invconset
.iter()
.map(|(k, &v)| (k.clone(), lit_to_i32(v)))
.collect(),
varlits_in_con: p
.varlits_in_con
.iter()
.map(|(&k, v)| {
(
lit_to_i32(k).to_string(),
v.iter().map(|&l| lit_to_i32(l)).collect(),
)
})
.collect(),
varset_lits: p.varset_lits.iter().map(|&l| lit_to_i32(l)).collect(),
varset_lits_neg: p.varset_lits_neg.iter().map(|&l| lit_to_i32(l)).collect(),
conset_lits: p.conset_lits.iter().map(|&l| lit_to_i32(l)).collect(),
order_encoding_map: p
.order_encoding_map
.iter()
.map(|(k, v)| (k.clone(), v.iter().map(|&l| lit_to_i32(l)).collect()))
.collect(),
inv_order_encoding_map: p
.inv_order_encoding_map
.iter()
.map(|(&k, v)| (lit_to_i32(k).to_string(), v.clone()))
.collect(),
order_encoding_all_lits: p
.order_encoding_all_lits
.iter()
.map(|&l| lit_to_i32(l))
.collect(),
reveal_map: p
.reveal_map
.iter()
.map(|(&k, &v)| (lit_to_i32(k).to_string(), lit_to_i32(v)))
.collect(),
})
}
}
fn str_to_i32(s: &str) -> Result<i32> {
s.parse::<i32>().context("Invalid literal string")
}
impl TryFrom<SerializablePuzzleParse> for PuzzleParse {
type Error = anyhow::Error;
fn try_from(s: SerializablePuzzleParse) -> Result<Self> {
let mut cnf = Cnf::new();
for clause in &s.cnf_clauses {
let lits: Result<Vec<Lit>> = clause.iter().map(|&i| i32_to_lit(i)).collect();
cnf.add_clause(Clause::from_iter(lits?));
}
let satinstance: SatInstance<BasicVarManager> = cnf.clone().into();
Ok(Self {
eprime: s.eprime.into(),
satinstance,
cnf: Some(Arc::new(cnf)),
litmap: s
.litmap
.into_iter()
.map(|(k, v)| Ok((k, i32_to_lit(v)?)))
.collect::<Result<_>>()?,
invlitmap: s
.invlitmap
.into_iter()
.map(|(k, v)| Ok((i32_to_lit(str_to_i32(&k)?)?, v)))
.collect::<Result<_>>()?,
domainmap: s.domainmap.into_iter().collect(),
conset: s
.conset
.into_iter()
.map(|(k, v)| Ok((i32_to_lit(str_to_i32(&k)?)?, v)))
.collect::<Result<_>>()?,
invconset: s
.invconset
.into_iter()
.map(|(k, v)| Ok((k, i32_to_lit(v)?)))
.collect::<Result<_>>()?,
varlits_in_con: s
.varlits_in_con
.into_iter()
.map(|(k, v)| {
let lits: Result<Vec<Lit>> = v.into_iter().map(i32_to_lit).collect();
Ok((i32_to_lit(str_to_i32(&k)?)?, lits?))
})
.collect::<Result<_>>()?,
varset_lits: s
.varset_lits
.into_iter()
.map(i32_to_lit)
.collect::<Result<_>>()?,
varset_lits_neg: s
.varset_lits_neg
.into_iter()
.map(i32_to_lit)
.collect::<Result<_>>()?,
conset_lits: s
.conset_lits
.into_iter()
.map(i32_to_lit)
.collect::<Result<_>>()?,
order_encoding_map: s
.order_encoding_map
.into_iter()
.map(|(k, v)| {
let lits: Result<HashSet<Lit>> = v.into_iter().map(i32_to_lit).collect();
Ok((k, lits?))
})
.collect::<Result<_>>()?,
inv_order_encoding_map: s
.inv_order_encoding_map
.into_iter()
.map(|(k, v)| Ok((i32_to_lit(str_to_i32(&k)?)?, v)))
.collect::<Result<_>>()?,
order_encoding_all_lits: s
.order_encoding_all_lits
.into_iter()
.map(i32_to_lit)
.collect::<Result<_>>()?,
reveal_map: s
.reveal_map
.into_iter()
.map(|(k, v)| Ok((i32_to_lit(str_to_i32(&k)?)?, i32_to_lit(v)?)))
.collect::<Result<_>>()?,
})
}
}
impl PuzzleParse {
pub fn save_to_json(&self, path: &Path) -> Result<()> {
let serializable = SerializablePuzzleParse::try_from(self)?;
let file = File::create(path).context("Failed to create output file")?;
let writer = BufWriter::new(file);
serde_json::to_writer_pretty(writer, &serializable)
.context("Failed to serialize puzzle")?;
Ok(())
}
pub fn load_from_json(path: &Path) -> Result<Self> {
let file = File::open(path).context("Failed to open input file")?;
let reader = BufReader::new(file);
let serializable: SerializablePuzzleParse =
serde_json::from_reader(reader).context("Failed to deserialize puzzle")?;
serializable.try_into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::problem::util::test_utils::build_puzzleparse;
#[test]
fn test_serialize_roundtrip_little() {
let original = build_puzzleparse("./tst/little1.eprime", "./tst/little1.param");
let temp_file = tempfile::Builder::new()
.prefix(".demystify-")
.tempfile_in(".")
.unwrap();
original.save_to_json(temp_file.path()).unwrap();
let loaded = PuzzleParse::load_from_json(temp_file.path()).unwrap();
assert_eq!(original.eprime.vars, loaded.eprime.vars);
assert_eq!(original.eprime.auxvars, loaded.eprime.auxvars);
assert_eq!(original.eprime.cons, loaded.eprime.cons);
assert_eq!(original.eprime.kind, loaded.eprime.kind);
assert_eq!(original.litmap, loaded.litmap);
assert_eq!(original.invlitmap, loaded.invlitmap);
assert_eq!(original.domainmap, loaded.domainmap);
assert_eq!(original.conset, loaded.conset);
assert_eq!(original.invconset, loaded.invconset);
assert_eq!(original.varset_lits, loaded.varset_lits);
assert_eq!(original.conset_lits, loaded.conset_lits);
}
#[test]
fn test_serialize_roundtrip_binairo() {
let original = build_puzzleparse("./tst/binairo.eprime", "./tst/binairo-1.param");
let temp_file = tempfile::Builder::new()
.prefix(".demystify-")
.tempfile_in(".")
.unwrap();
original.save_to_json(temp_file.path()).unwrap();
let loaded = PuzzleParse::load_from_json(temp_file.path()).unwrap();
assert_eq!(original.eprime.vars, loaded.eprime.vars);
assert_eq!(original.litmap, loaded.litmap);
assert_eq!(original.conset_lits.len(), loaded.conset_lits.len());
}
#[test]
fn test_serialize_solve_after_load() {
use crate::problem::planner::PuzzlePlanner;
use crate::problem::solver::PuzzleSolver;
use std::sync::Arc;
let original = build_puzzleparse("./tst/little1.eprime", "./tst/little1.param");
let temp_file = tempfile::Builder::new()
.prefix(".demystify-")
.tempfile_in(".")
.unwrap();
original.save_to_json(temp_file.path()).unwrap();
let loaded = PuzzleParse::load_from_json(temp_file.path()).unwrap();
let loaded = Arc::new(loaded);
let solver = PuzzleSolver::new(loaded).unwrap();
let mut planner = PuzzlePlanner::new(solver);
let steps = planner.quick_solve();
assert!(!steps.is_empty());
}
}