1use std::{
4 ffi::OsString,
5 fmt, fs, io,
6 path::{Path, PathBuf},
7};
8
9use rustsat::{
10 encodings::{cert::CollectClauses as CollectCertClauses, pb, CollectClauses},
11 instances::{fio, ManageVars, MultiOptInstance, Objective as RsObjective, ReindexVars},
12 types::{constraints::PbConstraint, Clause, Lit, RsHashMap},
13};
14
15use crate::types::{Instance, Objective, Parsed, Reindexer, VarManager};
16
17#[derive(Copy, Clone, PartialEq, Eq)]
18#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
19pub enum FileFormat {
20 Infer,
25 Dimacs,
27 Opb,
29}
30
31impl fmt::Display for FileFormat {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 match self {
34 FileFormat::Infer => write!(f, "infer"),
35 FileFormat::Dimacs => write!(f, "dimacs"),
36 FileFormat::Opb => write!(f, "opb"),
37 }
38 }
39}
40
41macro_rules! is_one_of {
42 ($a:expr, $($b:expr),*) => {
43 $( $a == $b || )* false
44 }
45}
46
47#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
48pub enum Error {
49 #[error("Cannot infer file format from extension {0:?}")]
50 UnknownFileExtension(OsString),
51 #[error("To infer the file format, the file needs to have a file extension")]
52 NoFileExtension,
53}
54
55pub fn parse<P: AsRef<Path>>(
56 inst_path: P,
57 file_format: FileFormat,
58 opb_opts: fio::opb::Options,
59) -> anyhow::Result<Parsed> {
60 let inst_path = inst_path.as_ref();
61 match file_format {
62 FileFormat::Infer => {
63 if let Some(ext) = inst_path.extension() {
64 let path_without_compr = inst_path.with_extension("");
65 let ext = if is_one_of!(ext, "gz", "bz2", "xz") {
66 match path_without_compr.extension() {
68 Some(ext) => ext,
69 None => anyhow::bail!(Error::NoFileExtension),
70 }
71 } else {
72 ext
73 };
74 if is_one_of!(ext, "mcnf", "bicnf", "wcnf", "cnf", "dimacs") {
75 clausal(inst_path)
76 } else if is_one_of!(ext, "opb", "mopb", "pbmo") {
77 pseudo_boolean(inst_path, opb_opts)
78 } else {
79 anyhow::bail!(Error::UnknownFileExtension(OsString::from(ext)))
80 }
81 } else {
82 anyhow::bail!(Error::NoFileExtension)
83 }
84 }
85 FileFormat::Dimacs => clausal(inst_path),
86 FileFormat::Opb => pseudo_boolean(inst_path, opb_opts),
87 }
88}
89
90fn clausal<P: AsRef<Path>>(input_path: P) -> anyhow::Result<Parsed> {
93 let (mut constr, objs) =
94 MultiOptInstance::<VarManager>::from_dimacs_path(input_path)?.decompose();
95 constr.var_manager_mut().mark_max_orig_var();
96 debug_assert_eq!(
97 constr.n_pbs(),
98 0,
99 "parsing should not convert constraint types yet"
100 );
101 debug_assert_eq!(
102 constr.n_cards(),
103 0,
104 "parsing should not convert constraint types yet"
105 );
106 let (constraints, vm) = constr.into_pbs();
107 Ok(Parsed {
108 constraints,
109 objs,
110 vm,
111 })
112}
113
114fn pseudo_boolean<P: AsRef<Path>>(
117 input_path: P,
118 opb_opts: fio::opb::Options,
119) -> anyhow::Result<Parsed> {
120 let (mut constr, objs) =
121 MultiOptInstance::<VarManager>::from_opb_path(input_path, opb_opts)?.decompose();
122 constr.var_manager_mut().mark_max_orig_var();
123 debug_assert_eq!(
124 constr.n_clauses(),
125 0,
126 "parsing should not convert constraint types yet"
127 );
128 debug_assert_eq!(
129 constr.n_cards(),
130 0,
131 "parsing should not convert constraint types yet"
132 );
133 let (constraints, vm) = constr.into_pbs();
134 Ok(Parsed {
135 constraints,
136 objs,
137 vm,
138 })
139}
140
141#[cfg(feature = "maxpre")]
142fn constraints_to_clausal(
143 constraints: Vec<PbConstraint>,
144 vm: &mut VarManager,
145) -> Result<rustsat::instances::Cnf, rustsat::OutOfMemory> {
146 use rustsat::{encodings::pb::default_encode_pb_constraint, instances::Cnf};
147
148 let mut cnf = Cnf::new();
149 for constr in constraints {
150 default_encode_pb_constraint(constr, &mut cnf, vm)?;
151 }
152 Ok(cnf)
153}
154
155#[cfg(feature = "maxpre")]
156pub fn max_pre(
157 parsed: Parsed,
158 techniques: &str,
159 reindexing: bool,
160) -> Result<(maxpre::MaxPre, Instance), rustsat::OutOfMemory> {
161 use maxpre::PreproClauses;
162 let Parsed {
163 constraints,
164 objs,
165 mut vm,
166 ..
167 } = parsed;
168 let cnf = constraints_to_clausal(constraints, &mut vm)?;
169 let mut prepro = maxpre::MaxPre::new(
170 cnf,
171 objs.into_iter().map(|o| o.into_soft_cls()).collect(),
172 !reindexing,
173 );
174 prepro.preprocess(techniques, 0, 1e9);
175 let (cnf, objs) = prepro.prepro_instance();
176 let objs: Vec<_> = objs
177 .into_iter()
178 .enumerate()
179 .map(|(idx, (softs, offset))| {
180 Objective::new(
181 softs.into_iter().map(|(cl, w)| {
182 debug_assert_eq!(cl.len(), 1);
183 (!cl[0], w)
184 }),
185 offset,
186 idx,
187 )
188 })
189 .collect();
190 let max_var = cnf.iter().fold(rustsat::types::Var::new(0), |max, cl| {
191 cl.iter().fold(max, |max, l| std::cmp::max(max, l.var()))
192 });
193 let vm = VarManager::new(max_var, max_var);
194 Ok((
195 prepro,
196 Instance {
197 clauses: cnf.into_iter().map(|cl| (cl, None)).collect(),
198 objs,
199 vm,
200 },
201 ))
202}
203
204struct Collector(Vec<(Clause, Option<pigeons::AbsConstraintId>)>);
205
206impl CollectClauses for Collector {
207 fn n_clauses(&self) -> usize {
208 self.0.len()
209 }
210
211 fn extend_clauses<T>(&mut self, cl_iter: T) -> Result<(), rustsat::OutOfMemory>
212 where
213 T: IntoIterator<Item = Clause>,
214 {
215 self.0.extend(cl_iter.into_iter().map(|cl| (cl, None)));
216 Ok(())
217 }
218}
219
220impl CollectCertClauses for Collector {
221 fn extend_cert_clauses<T>(&mut self, cl_iter: T) -> Result<(), rustsat::OutOfMemory>
222 where
223 T: IntoIterator<Item = (Clause, pigeons::AbsConstraintId)>,
224 {
225 self.0
226 .extend(cl_iter.into_iter().map(|(cl, id)| (cl, Some(id))));
227 Ok(())
228 }
229}
230
231pub fn to_clausal(
232 parsed: Parsed,
233 proof_paths: &Option<(PathBuf, Option<PathBuf>)>,
234) -> anyhow::Result<(Option<pigeons::Proof<io::BufWriter<fs::File>>>, Instance)> {
235 let Parsed {
236 mut constraints,
237 objs,
238 mut vm,
239 ..
240 } = parsed;
241 let mut blits = RsHashMap::default();
242 let objs: Vec<_> = objs
244 .into_iter()
245 .enumerate()
246 .map(|(idx, o)| process_objective(o, idx, &mut constraints, &mut blits, &mut vm))
247 .collect();
248 let mut proof = None;
249 let mut collector = Collector(vec![]);
251 if let Some((proof_path, veripb_input_path)) = proof_paths {
252 let n_constraints = constraints.iter().fold(0, |s, c| {
253 if matches!(c, PbConstraint::Eq(_)) {
254 s + 2
255 } else {
256 s + 1
257 }
258 });
259
260 if let Some(veripb_input_path) = veripb_input_path {
261 let mut writer = io::BufWriter::new(fs::File::create(veripb_input_path)?);
263 let iter = constraints
264 .iter()
265 .map(|c| fio::opb::FileLine::<Option<_>>::Pb(c.clone()));
266 fio::opb::write_opb_lines(&mut writer, iter, fio::opb::Options::default())?;
267 }
268
269 let mut the_proof = crate::algs::proofs::init_proof(
270 io::BufWriter::new(fs::File::create(proof_path)?),
271 n_constraints,
272 &objs,
273 )?;
274
275 let mut id = pigeons::AbsConstraintId::new(1);
276 for constr in constraints {
277 let eq = matches!(constr, PbConstraint::Eq(_));
278 pb::cert::default_encode_pb_constraint(
279 (constr, id),
280 &mut collector,
281 &mut vm,
282 &mut the_proof,
283 )?;
284 id += 1 + usize::from(eq);
285 }
286 #[cfg(feature = "verbose-proofs")]
287 the_proof.comment(&"end OPB translation")?;
288 proof = Some(the_proof);
289 } else {
290 for constr in constraints {
291 pb::default_encode_pb_constraint(constr, &mut collector, &mut vm)?;
292 }
293 }
294 vm.mark_max_enc_var();
295 Ok((
296 proof,
297 Instance {
298 clauses: collector.0,
299 objs,
300 vm,
301 },
302 ))
303}
304
305fn process_objective<VM: ManageVars>(
306 obj: RsObjective,
307 idx: usize,
308 constrs: &mut Vec<PbConstraint>,
309 blits: &mut RsHashMap<Clause, Lit>,
310 vm: &mut VM,
311) -> Objective {
312 let (soft_cls, offset) = obj.into_soft_cls();
313 Objective::new(
314 soft_cls.into_iter().map(|(mut cl, w)| {
315 debug_assert!(cl.len() > 0);
316 if cl.len() == 1 {
317 return (!cl[0], w);
318 }
319 if let Some(&blit) = blits.get(&cl) {
320 return (blit, w);
321 }
322 let blit = vm.new_var().pos_lit();
323 blits.insert(cl.clone(), blit);
326 cl.add(blit);
328 constrs.push(cl.into());
329 (blit, w)
330 }),
331 offset,
332 idx,
333 )
334}
335
336pub fn reindexing(inst: Instance) -> (Reindexer, Instance) {
337 let Instance {
338 mut clauses,
339 mut objs,
340 vm,
341 ..
342 } = inst;
343 let mut reindexer = Reindexer::new(vm.max_orig_var());
344 for obj in &mut objs {
345 let new_obj = Objective::new(
346 obj.iter().map(|(l, w)| (reindexer.reindex_lit(l), w)),
347 obj.offset(),
348 obj.idx(),
349 );
350 *obj = new_obj;
351 }
352 for (cl, _) in &mut clauses {
353 for l in cl {
354 *l = reindexer.reindex_lit(*l);
355 }
356 }
357 let max_var = reindexer.max_var().unwrap();
358 let vm = VarManager::new(max_var, max_var);
359 (reindexer, Instance { clauses, objs, vm })
360}