1use std::{fs, io};
12
13use cadical_veripb_tracer::CadicalCertCollector;
14use pigeons::ConstraintId;
15use rustsat::{
16 clause,
17 encodings::{
18 self,
19 card::{self, Totalizer},
20 pb::{self, GeneralizedTotalizer},
21 Monotone,
22 },
23 solvers::{
24 DefaultInitializer, Initialize, Solve, SolveIncremental, SolveStats, SolverResult,
25 SolverStats,
26 },
27 types::{Assignment, Clause, Lit, Var},
28};
29use scuttle_proc::KernelFunctions;
30
31use crate::{
32 options::{AfterCbOptions, CoreBoostingOptions},
33 termination::ensure,
34 types::{NonDomPoint, ParetoFront, VarManager},
35 EncodingStats, ExtendedSolveStats, KernelFunctions, KernelOptions, Limits,
36 MaybeTerminatedError::{self, Done},
37 Phase,
38};
39
40use super::{coreboosting::MergeOllRef, proofs, CoreBoost, Kernel, ObjEncoding, Objective};
41
42#[derive(KernelFunctions)]
53pub struct LowerBounding<
54 O,
55 PBE = GeneralizedTotalizer,
56 CE = Totalizer,
57 ProofW = io::BufWriter<fs::File>,
58 OInit = DefaultInitializer,
59 BCG = fn(Assignment) -> Clause,
60> where
61 ProofW: io::Write,
62{
63 kernel: Kernel<O, ProofW, OInit, BCG>,
65 obj_encs: Vec<ObjEncoding<PBE, CE>>,
67 fence: Fence,
69 pareto_front: ParetoFront,
71}
72
73impl<'learn, 'term, ProofW, OInit, BCG> super::Solve
74 for LowerBounding<
75 rustsat_cadical::CaDiCaL<'term, 'learn>,
76 GeneralizedTotalizer,
77 Totalizer,
78 ProofW,
79 OInit,
80 BCG,
81 >
82where
83 BCG: Fn(Assignment) -> Clause,
84 ProofW: io::Write + 'static,
85{
86 fn solve(&mut self, limits: Limits) -> MaybeTerminatedError {
87 self.kernel.start_solving(limits);
88 self.alg_main()
89 }
90
91 fn all_stats(
92 &self,
93 ) -> (
94 crate::Stats,
95 Option<SolverStats>,
96 Option<Vec<EncodingStats>>,
97 ) {
98 use crate::ExtendedSolveStats;
99 (
100 self.kernel.stats,
101 Some(self.oracle_stats()),
102 Some(self.encoding_stats()),
103 )
104 }
105}
106
107impl<'learn, 'term, ProofW, OInit, BCG> super::Init
108 for LowerBounding<
109 rustsat_cadical::CaDiCaL<'learn, 'term>,
110 GeneralizedTotalizer,
111 Totalizer,
112 ProofW,
113 OInit,
114 BCG,
115 >
116where
117 ProofW: io::Write + 'static,
118 OInit: Initialize<rustsat_cadical::CaDiCaL<'learn, 'term>>,
119 BCG: Fn(Assignment) -> Clause,
120{
121 type Oracle = rustsat_cadical::CaDiCaL<'learn, 'term>;
122 type BlockClauseGen = BCG;
123
124 fn new<Cls>(
127 clauses: Cls,
128 objs: Vec<Objective>,
129 var_manager: VarManager,
130 opts: KernelOptions,
131 block_clause_gen: BCG,
132 ) -> anyhow::Result<Self>
133 where
134 Cls: IntoIterator<Item = Clause>,
135 {
136 let kernel = Kernel::new(clauses, objs, var_manager, block_clause_gen, opts)?;
137 Self::init(kernel)
138 }
139}
140
141impl<'term, 'learn, ProofW, OInit, BCG> super::InitCert
142 for LowerBounding<
143 rustsat_cadical::CaDiCaL<'term, 'learn>,
144 GeneralizedTotalizer,
145 Totalizer,
146 ProofW,
147 OInit,
148 BCG,
149 >
150where
151 OInit: Initialize<rustsat_cadical::CaDiCaL<'term, 'learn>>,
152 ProofW: io::Write + 'static,
153 BCG: Fn(Assignment) -> Clause,
154{
155 type ProofWriter = ProofW;
156
157 fn new_cert<Cls>(
160 clauses: Cls,
161 objs: Vec<Objective>,
162 var_manager: VarManager,
163 opts: KernelOptions,
164 proof: pigeons::Proof<Self::ProofWriter>,
165 block_clause_gen: BCG,
166 ) -> anyhow::Result<Self>
167 where
168 Cls: IntoIterator<Item = (Clause, pigeons::AbsConstraintId)>,
169 {
170 let kernel = Kernel::new_cert(clauses, objs, var_manager, block_clause_gen, proof, opts)?;
171 Self::init(kernel)
172 }
173}
174
175impl<O, PBE, CE, ProofW, OInit, BCG> ExtendedSolveStats
176 for LowerBounding<O, PBE, CE, ProofW, OInit, BCG>
177where
178 O: SolveStats,
179 ProofW: io::Write,
180 PBE: encodings::EncodeStats,
181 CE: encodings::EncodeStats,
182{
183 fn oracle_stats(&self) -> SolverStats {
184 self.kernel.oracle.stats()
185 }
186
187 fn encoding_stats(&self) -> Vec<EncodingStats> {
188 self.kernel
189 .objs
190 .iter()
191 .zip(self.obj_encs.iter())
192 .map(|(obj, enc)| {
193 let mut s = EncodingStats {
194 offset: obj.offset(),
195 ..Default::default()
196 };
197 if let Objective::Unweighted { unit_weight, .. } = obj {
198 s.unit_weight = Some(*unit_weight);
199 };
200 match enc {
201 ObjEncoding::Weighted(enc, _) => {
202 s.n_vars = enc.n_vars();
203 s.n_clauses = enc.n_clauses()
204 }
205 ObjEncoding::Unweighted(enc, _) => {
206 s.n_vars = enc.n_vars();
207 s.n_clauses = enc.n_clauses()
208 }
209 ObjEncoding::Constant => (),
210 };
211 s
212 })
213 .collect()
214 }
215}
216
217impl<O, PBE, CE, ProofW, OInit, BCG> LowerBounding<O, PBE, CE, ProofW, OInit, BCG>
218where
219 ProofW: io::Write + 'static,
220 PBE: pb::BoundUpperIncremental + FromIterator<(Lit, usize)> + Monotone,
221 CE: card::BoundUpperIncremental + FromIterator<Lit> + Monotone,
222{
223 fn init(mut kernel: Kernel<O, ProofW, OInit, BCG>) -> anyhow::Result<Self> {
225 let fence_data = Vec::with_capacity(kernel.objs.len());
227 let obj_encs = kernel
228 .objs
229 .iter()
230 .map(|obj| match obj {
231 Objective::Weighted { lits, .. } => ObjEncoding::<PBE, CE>::new_weighted(
232 lits.iter().map(|(&l, &w)| (l, w)),
233 kernel.opts.reserve_enc_vars,
234 &mut kernel.var_manager,
235 ),
236 Objective::Unweighted { lits, .. } => ObjEncoding::<PBE, CE>::new_unweighted(
237 lits.iter().copied(),
238 kernel.opts.reserve_enc_vars,
239 &mut kernel.var_manager,
240 ),
241 Objective::Constant { .. } => ObjEncoding::Constant,
242 })
243 .collect();
244 Ok(Self {
245 kernel,
246 obj_encs,
247 fence: Fence { data: fence_data },
248 pareto_front: Default::default(),
249 })
250 }
251}
252
253impl<'learn, 'term, ProofW, OInit, BCG>
254 LowerBounding<
255 rustsat_cadical::CaDiCaL<'learn, 'term>,
256 GeneralizedTotalizer,
257 Totalizer,
258 ProofW,
259 OInit,
260 BCG,
261 >
262where
263 ProofW: io::Write + 'static,
264 BCG: Fn(Assignment) -> Clause,
265{
266 fn alg_main(&mut self) -> MaybeTerminatedError {
268 debug_assert_eq!(self.obj_encs.len(), self.kernel.stats.n_objs);
269 self.kernel.log_routine_start("lower-bounding")?;
270 if self.fence.data.is_empty() {
272 for enc in self.obj_encs.iter_mut() {
273 if let Some(proofs::ProofStuff { pt_handle, .. }) = &self.kernel.proof_stuff {
274 let proof: *mut _ = self.kernel.oracle.proof_tracer_mut(pt_handle).proof_mut();
275 let mut collector =
276 CadicalCertCollector::new(&mut self.kernel.oracle, pt_handle);
277 enc.encode_ub_change_cert(
278 enc.offset()..enc.offset() + 1,
279 &mut collector,
280 &mut self.kernel.var_manager,
281 unsafe { &mut *proof },
282 )?;
283 } else {
284 enc.encode_ub_change(
285 enc.offset()..enc.offset() + 1,
286 &mut self.kernel.oracle,
287 &mut self.kernel.var_manager,
288 )?;
289 }
290 let assumps = enc.enforce_ub(enc.offset()).unwrap();
291 self.fence.data.push((enc.offset(), assumps));
292 }
293 }
294 loop {
295 let assumps: Vec<_> = self.fence.assumps().collect();
296 let res = self.kernel.solve_assumps(&assumps)?;
297 match res {
298 SolverResult::Sat => self.kernel.harvest(
299 &self.fence,
300 &mut self.obj_encs,
301 &[],
302 &mut self.pareto_front,
303 )?,
304 SolverResult::Unsat => {
305 let core = self.kernel.oracle.core()?;
306 if core.is_empty() {
307 self.kernel.log_routine_end()?;
308 return Done(());
309 }
310 #[cfg(debug_assertions)]
311 let old_fence = self.fence.bounds();
312 self.kernel
313 .update_fence(&mut self.fence, core, &mut self.obj_encs)?;
314 #[cfg(debug_assertions)]
315 {
316 let new_fence = self.fence.bounds();
317 let mut increased = false;
318 for idx in 0..old_fence.len() {
319 debug_assert!(old_fence[idx] <= new_fence[idx]);
320 if old_fence[idx] < new_fence[idx] {
321 increased = true;
322 }
323 }
324 if !increased {
325 panic!("fence has not increased");
326 }
327 }
328 }
329 SolverResult::Interrupted => panic!("should have errored before"),
330 }
331 }
332 }
333}
334
335impl<'learn, 'term, PBE, CE, ProofW, OInit, BCG> CoreBoost
336 for LowerBounding<rustsat_cadical::CaDiCaL<'learn, 'term>, PBE, CE, ProofW, OInit, BCG>
337where
338 ProofW: io::Write + 'static,
339 (PBE, CE): MergeOllRef<PBE = PBE, CE = CE>,
340 OInit: Initialize<rustsat_cadical::CaDiCaL<'learn, 'term>>,
341{
342 fn core_boost(&mut self, opts: CoreBoostingOptions) -> MaybeTerminatedError<bool> {
343 ensure!(
344 self.kernel.stats.n_solve_calls == 0,
345 "cannot perform core boosting after solve has been called"
346 );
347 let Some(cb_res) = self.kernel.core_boost()? else {
348 return Done(false);
349 };
350 self.kernel.check_termination()?;
351 let reset_dbs = match &opts.after {
352 AfterCbOptions::Nothing => false,
353 AfterCbOptions::Reset => {
354 self.kernel.reset_oracle(true)?;
355 self.kernel.check_termination()?;
356 true
357 }
358 #[cfg(feature = "maxpre")]
359 AfterCbOptions::Inpro(techs) => {
360 self.obj_encs = self.kernel.inprocess(techs, cb_res)?;
361 self.kernel.check_termination()?;
362 return Done(true);
363 }
364 };
365 self.kernel.log_routine_start("merge encodings")?;
366 for (oidx, (reform, mut tot_db)) in cb_res.into_iter().enumerate() {
367 if reset_dbs {
368 debug_assert!(self.kernel.proof_stuff.is_none());
369 tot_db.reset_vars();
370 }
371 if !matches!(self.kernel.objs[oidx], Objective::Constant { .. }) {
372 if let Some(proofs::ProofStuff { pt_handle, .. }) = &self.kernel.proof_stuff {
373 if !reform.reformulations.is_empty() {
374 let proof = self.kernel.oracle.proof_tracer_mut(pt_handle).proof_mut();
376 #[cfg(feature = "verbose-proofs")]
377 proof.comment(&format_args!(
378 "deleting remaining reformulation constraints from OLL of objective {oidx}"
379 ))?;
380 proof.delete_ids::<Var, Clause, _, _>(
381 reform
382 .reformulations
383 .values()
384 .map(|re| ConstraintId::from(re.proof_id.unwrap())),
385 None,
386 )?;
387 }
388 }
389
390 self.obj_encs[oidx] = <(PBE, CE)>::merge(reform, tot_db, opts.rebase);
391 }
392 self.kernel.check_termination()?;
393 }
394 self.kernel.log_routine_end()?;
395 Done(true)
396 }
397}
398
399pub(crate) struct Fence {
401 pub data: Vec<(usize, Vec<Lit>)>,
403}
404
405impl Fence {
406 pub fn assumps(&self) -> impl Iterator<Item = Lit> + '_ {
407 self.data
408 .iter()
409 .flat_map(|(_, assumps)| assumps.iter().copied())
410 }
411
412 pub fn bounds(&self) -> Vec<usize> {
413 self.data.iter().map(|&(b, _)| b).collect()
414 }
415}
416
417impl<'learn, 'term, ProofW, OInit, BCG>
418 Kernel<rustsat_cadical::CaDiCaL<'learn, 'term>, ProofW, OInit, BCG>
419where
420 ProofW: io::Write + 'static,
421{
422 pub fn update_fence(
423 &mut self,
424 fence: &mut Fence,
425 core: Vec<Lit>,
426 obj_encs: &mut [ObjEncoding<GeneralizedTotalizer, Totalizer>],
427 ) -> MaybeTerminatedError {
428 let mut found = vec![false; fence.data.len()];
429 'core: for clit in core {
430 for (obj_idx, (bound, assumps)) in fence.data.iter_mut().enumerate() {
431 if found[obj_idx] {
432 continue;
434 }
435 let mut matches = false;
436 for alit in assumps.iter() {
437 if !*alit == clit {
438 matches = true;
439 break;
440 }
441 }
442 if matches {
443 found[obj_idx] = true;
444 let enc = &mut obj_encs[obj_idx];
446 *bound = enc.next_higher(*bound);
447 self.extend_encoding(enc, *bound..*bound + 1)?;
448 *assumps = enc.enforce_ub(*bound).unwrap();
449 continue 'core;
450 }
451 }
452 }
453 if let Some(logger) = &mut self.logger {
454 logger.log_fence(&fence.bounds())?
455 }
456 Done(())
457 }
458}
459
460impl<'term, 'learn, ProofW, OInit, BCG>
461 Kernel<rustsat_cadical::CaDiCaL<'term, 'learn>, ProofW, OInit, BCG>
462where
463 ProofW: io::Write + 'static,
464 BCG: Fn(Assignment) -> Clause,
465{
466 pub fn harvest<Col>(
468 &mut self,
469 fence: &Fence,
470 obj_encs: &mut [ObjEncoding<GeneralizedTotalizer, Totalizer>],
471 base_assumps: &[Lit],
472 collector: &mut Col,
473 ) -> MaybeTerminatedError
474 where
475 Col: Extend<NonDomPoint>,
476 {
477 debug_assert_eq!(obj_encs.len(), self.stats.n_objs);
478 self.log_routine_start("harvest")?;
479 let mut assumps = Vec::from(base_assumps);
480 loop {
481 assumps.drain(base_assumps.len()..);
482 assumps.extend(fence.assumps());
484 let res = self.solve_assumps(&assumps)?;
485 if SolverResult::Unsat == res {
486 self.log_routine_end()?;
487 return Done(());
488 }
489 self.check_termination()?;
490
491 let (costs, solution) = self.get_solution_and_internal_costs(
493 self.opts
494 .heuristic_improvements
495 .solution_tightening
496 .wanted(Phase::OuterLoop),
497 )?;
498 self.log_candidate(&costs, Phase::OuterLoop)?;
499 self.check_termination()?;
500 self.phase_solution(solution.clone())?;
501 let (costs, solution, block_switch) =
502 self.p_minimization(costs, solution, base_assumps, obj_encs)?;
503
504 let assumps: Vec<_> = self.enforce_dominating(&costs, obj_encs)?.collect();
505 self.yield_solutions(costs.clone(), &assumps, solution.clone(), collector)?;
506
507 if let Some((block_lit, ids)) = block_switch {
509 if let Some(proof_stuff) = &mut self.proof_stuff {
510 use pigeons::{ConstraintId, Derivation, ProofGoal, ProofGoalId};
511 use rustsat::encodings::cert::CollectClauses;
512
513 let (reified_cut, reified_assump_ids) = ids.unwrap();
514 let id = proofs::certify_pmin_cut(
515 obj_encs,
516 &self.objs,
517 &costs,
518 &solution,
519 self.var_manager.max_enc_var(),
520 proof_stuff,
521 &mut self.oracle,
522 )?;
523 let proof = self
524 .oracle
525 .proof_tracer_mut(&proof_stuff.pt_handle)
526 .proof_mut();
527 let hints = [ConstraintId::last(2), ConstraintId::last(1), id.into()]
528 .into_iter()
529 .chain(reified_assump_ids.iter().map(|id| ConstraintId::from(*id)));
530 let unit = clause![block_lit];
531 let unit_id = proof.redundant(
532 &unit,
533 [],
534 [ProofGoal::new(
535 ProofGoalId::from(ConstraintId::from(reified_cut)),
536 [Derivation::Rup(clause![], hints.collect())],
537 )
538 .into()],
539 )?;
540 cadical_veripb_tracer::CadicalCertCollector::new(
541 &mut self.oracle,
542 &proof_stuff.pt_handle,
543 )
544 .add_cert_clause(unit, unit_id)?;
545 } else {
546 self.oracle.add_unit(block_lit)?;
547 }
548 }
549 }
550 }
551}