scuttle_core/algs/
pminimal.rs

1//! # $P$-Minimal Model Enumeration for Multi-Objective Optimization
2//!
3//! This module implements $P$-minimal model enumeration as an algorithm for
4//! solving multi-objective optimization problems expressed as boolean logic.
5//! Instead of using the order encoding as in \[1\], any cardinality (for
6//! unweighted objectives) or pseudo-boolean encoding from
7//! [RustSAT](https://github.com/chrjabs/rustsat) can be used. The actual
8//! enumeration algorithm follows \[2\].
9//!
10//! ## References
11//!
12//! - \[1\] Takehide Soh and Mutsunori Banbara and Naoyuki Tamura and Daniel Le
13//!     Berre: _Solving Multiobjective Discrete Optimization Problems with
14//!     Propositional Minimal Model Generation_, CP 2017.
15//! - \[2\] Miyuki Koshimura and Hidetomo Nabeshima and Hiroshi Fujita and Ryuzo
16//!     Hasegawa: _Minimal Model Generation with Respect to an Atom Set_, FTP
17//!     2009.
18use std::{fs, io};
19
20use cadical_veripb_tracer::CadicalCertCollector;
21use pigeons::{AbsConstraintId, ConstraintId};
22use rustsat::{
23    clause,
24    encodings::{
25        self, atomics,
26        card::{self, Totalizer},
27        pb::{self, GeneralizedTotalizer},
28        Monotone,
29    },
30    instances::ManageVars,
31    solvers::{
32        DefaultInitializer, Initialize, Solve, SolveIncremental, SolveStats, SolverResult,
33        SolverStats,
34    },
35    types::{Assignment, Clause, Lit, Var},
36};
37use scuttle_proc::{oracle_bounds, KernelFunctions};
38
39use crate::{
40    options::{AfterCbOptions, CoreBoostingOptions, EnumOptions},
41    termination::ensure,
42    types::{ParetoFront, VarManager},
43    EncodingStats, ExtendedSolveStats, KernelFunctions, KernelOptions, Limits,
44    MaybeTerminatedError::{self, Done},
45    Phase,
46};
47
48use super::{coreboosting::MergeOllRef, proofs, CoreBoost, Kernel, ObjEncoding, Objective};
49
50/// The $P$-minimal algorithm type
51///
52/// # Generics
53///
54/// - `O`: the SAT solver oracle
55/// - `PBE`: pseudo-Boolean objective encoding
56/// - `CE`: cardinality objective encoding
57/// - `ProofW`: the proof writer
58/// - `OInit`: the oracle initializer
59/// - `BCG`: the blocking clause generator
60#[derive(KernelFunctions)]
61pub struct PMinimal<
62    O,
63    PBE = GeneralizedTotalizer,
64    CE = Totalizer,
65    ProofW = io::BufWriter<fs::File>,
66    OInit = DefaultInitializer,
67    BCG = fn(Assignment) -> Clause,
68> where
69    ProofW: io::Write,
70{
71    /// The solver kernel
72    kernel: Kernel<O, ProofW, OInit, BCG>,
73    /// A cardinality or pseudo-boolean encoding for each objective
74    obj_encs: Vec<ObjEncoding<PBE, CE>>,
75    /// The Pareto front discovered so far
76    pareto_front: ParetoFront,
77}
78
79impl<'learn, 'term, ProofW, OInit, BCG> super::Solve
80    for PMinimal<
81        rustsat_cadical::CaDiCaL<'term, 'learn>,
82        GeneralizedTotalizer,
83        Totalizer,
84        ProofW,
85        OInit,
86        BCG,
87    >
88where
89    BCG: Fn(Assignment) -> Clause,
90    ProofW: io::Write + 'static,
91{
92    fn solve(&mut self, limits: Limits) -> MaybeTerminatedError {
93        self.kernel.start_solving(limits);
94        self.alg_main()
95    }
96
97    fn all_stats(
98        &self,
99    ) -> (
100        crate::Stats,
101        Option<SolverStats>,
102        Option<Vec<EncodingStats>>,
103    ) {
104        use crate::ExtendedSolveStats;
105        (
106            self.kernel.stats,
107            Some(self.oracle_stats()),
108            Some(self.encoding_stats()),
109        )
110    }
111}
112
113#[oracle_bounds]
114impl<O, PBE, CE, ProofW, OInit, BCG> super::Init for PMinimal<O, PBE, CE, ProofW, OInit, BCG>
115where
116    O: SolveIncremental,
117    ProofW: io::Write,
118    PBE: pb::BoundUpperIncremental + FromIterator<(Lit, usize)> + Monotone,
119    CE: card::BoundUpperIncremental + FromIterator<Lit> + Monotone,
120    OInit: Initialize<O>,
121    BCG: Fn(Assignment) -> Clause,
122{
123    type Oracle = O;
124    type BlockClauseGen = BCG;
125
126    /// Initializes a default solver with a configured oracle and options. The
127    /// oracle should _not_ have any clauses loaded yet.
128    fn new<Cls>(
129        clauses: Cls,
130        objs: Vec<Objective>,
131        var_manager: VarManager,
132        opts: KernelOptions,
133        block_clause_gen: BCG,
134    ) -> anyhow::Result<Self>
135    where
136        Cls: IntoIterator<Item = Clause>,
137    {
138        let kernel = Kernel::new(clauses, objs, var_manager, block_clause_gen, opts)?;
139        Ok(Self::init(kernel))
140    }
141}
142
143impl<'term, 'learn, PBE, CE, ProofW, OInit, BCG> super::InitCert
144    for PMinimal<rustsat_cadical::CaDiCaL<'term, 'learn>, PBE, CE, ProofW, OInit, BCG>
145where
146    PBE: pb::BoundUpperIncremental + FromIterator<(Lit, usize)> + Monotone,
147    CE: card::BoundUpperIncremental + FromIterator<Lit> + Monotone,
148    OInit: Initialize<rustsat_cadical::CaDiCaL<'term, 'learn>>,
149    ProofW: io::Write + 'static,
150    BCG: Fn(Assignment) -> Clause,
151{
152    type ProofWriter = ProofW;
153
154    /// Initializes a default solver with a configured oracle and options. The
155    /// oracle should _not_ have any clauses loaded yet.
156    fn new_cert<Cls>(
157        clauses: Cls,
158        objs: Vec<Objective>,
159        var_manager: VarManager,
160        opts: KernelOptions,
161        proof: pigeons::Proof<Self::ProofWriter>,
162        block_clause_gen: BCG,
163    ) -> anyhow::Result<Self>
164    where
165        Cls: IntoIterator<Item = (Clause, pigeons::AbsConstraintId)>,
166    {
167        let kernel = Kernel::new_cert(clauses, objs, var_manager, block_clause_gen, proof, opts)?;
168        Ok(Self::init(kernel))
169    }
170}
171
172impl<O, PBE, CE, ProofW, OInit, BCG> ExtendedSolveStats for PMinimal<O, PBE, CE, ProofW, OInit, BCG>
173where
174    O: SolveStats,
175    ProofW: io::Write,
176    PBE: encodings::EncodeStats,
177    CE: encodings::EncodeStats,
178{
179    fn oracle_stats(&self) -> SolverStats {
180        self.kernel.oracle.stats()
181    }
182
183    fn encoding_stats(&self) -> Vec<EncodingStats> {
184        self.kernel
185            .objs
186            .iter()
187            .zip(self.obj_encs.iter())
188            .map(|(obj, enc)| {
189                let mut s = EncodingStats {
190                    offset: obj.offset(),
191                    ..Default::default()
192                };
193                if let Objective::Unweighted { unit_weight, .. } = obj {
194                    s.unit_weight = Some(*unit_weight);
195                };
196                match enc {
197                    ObjEncoding::Weighted(enc, _) => {
198                        s.n_vars = enc.n_vars();
199                        s.n_clauses = enc.n_clauses()
200                    }
201                    ObjEncoding::Unweighted(enc, _) => {
202                        s.n_vars = enc.n_vars();
203                        s.n_clauses = enc.n_clauses()
204                    }
205                    ObjEncoding::Constant => (),
206                };
207                s
208            })
209            .collect()
210    }
211}
212
213impl<O, PBE, CE, ProofW, OInit, BCG> PMinimal<O, PBE, CE, ProofW, OInit, BCG>
214where
215    ProofW: io::Write,
216    PBE: pb::BoundUpperIncremental + FromIterator<(Lit, usize)> + Monotone,
217    CE: card::BoundUpperIncremental + FromIterator<Lit> + Monotone,
218{
219    /// Initializes the solver
220    fn init(mut kernel: Kernel<O, ProofW, OInit, BCG>) -> Self {
221        // Initialize objective encodings
222        let obj_encs = kernel
223            .objs
224            .iter()
225            .map(|obj| match obj {
226                Objective::Weighted { lits, .. } => ObjEncoding::new_weighted(
227                    lits.iter().map(|(&l, &w)| (l, w)),
228                    kernel.opts.reserve_enc_vars,
229                    &mut kernel.var_manager,
230                ),
231                Objective::Unweighted { lits, .. } => ObjEncoding::new_unweighted(
232                    lits.iter().copied(),
233                    kernel.opts.reserve_enc_vars,
234                    &mut kernel.var_manager,
235                ),
236                Objective::Constant { .. } => ObjEncoding::Constant,
237            })
238            .collect();
239        Self {
240            kernel,
241            obj_encs,
242            pareto_front: Default::default(),
243        }
244    }
245}
246
247impl<'learn, 'term, ProofW, OInit, BCG>
248    PMinimal<
249        rustsat_cadical::CaDiCaL<'learn, 'term>,
250        GeneralizedTotalizer,
251        Totalizer,
252        ProofW,
253        OInit,
254        BCG,
255    >
256where
257    BCG: Fn(Assignment) -> Clause,
258    ProofW: io::Write + 'static,
259{
260    /// The solving algorithm main routine.
261    fn alg_main(&mut self) -> MaybeTerminatedError {
262        debug_assert_eq!(self.obj_encs.len(), self.kernel.stats.n_objs);
263        self.kernel.log_routine_start("p-minimal")?;
264        loop {
265            // Find minimization starting point
266            let res = self.kernel.solve()?;
267            if SolverResult::Unsat == res {
268                self.kernel.log_routine_end()?;
269                return Done(());
270            }
271            self.kernel.check_termination()?;
272
273            // Minimize solution
274            let (costs, solution) = self.kernel.get_solution_and_internal_costs(
275                self.kernel
276                    .opts
277                    .heuristic_improvements
278                    .solution_tightening
279                    .wanted(Phase::OuterLoop),
280            )?;
281            self.kernel.log_candidate(&costs, Phase::OuterLoop)?;
282            self.kernel.check_termination()?;
283            self.kernel.phase_solution(solution.clone())?;
284            let (costs, solution, block_switch) =
285                self.kernel
286                    .p_minimization(costs, solution, &[], &mut self.obj_encs)?;
287
288            let assumps: Vec<_> = self
289                .kernel
290                .enforce_dominating(&costs, &mut self.obj_encs)?
291                .collect();
292            self.kernel.yield_solutions(
293                costs.clone(),
294                &assumps,
295                solution.clone(),
296                &mut self.pareto_front,
297            )?;
298
299            // Block last Pareto point, if temporarily blocked
300            if let Some((block_lit, ids)) = block_switch {
301                if let Some(proof_stuff) = &mut self.kernel.proof_stuff {
302                    use pigeons::{ConstraintId, Derivation, ProofGoal, ProofGoalId};
303                    use rustsat::encodings::cert::CollectClauses;
304
305                    let (reified_cut, reified_assump_ids) = ids.unwrap();
306                    let id = proofs::certify_pmin_cut(
307                        &self.obj_encs,
308                        &self.kernel.objs,
309                        &costs,
310                        &solution,
311                        self.kernel.var_manager.max_enc_var(),
312                        proof_stuff,
313                        &mut self.kernel.oracle,
314                    )?;
315                    let proof = self
316                        .kernel
317                        .oracle
318                        .proof_tracer_mut(&proof_stuff.pt_handle)
319                        .proof_mut();
320                    let hints = [ConstraintId::last(2), ConstraintId::last(1), id.into()]
321                        .into_iter()
322                        .chain(reified_assump_ids.iter().map(|id| ConstraintId::from(*id)));
323                    let unit = clause![block_lit];
324                    let unit_id = proof.redundant(
325                        &unit,
326                        [],
327                        [ProofGoal::new(
328                            ProofGoalId::from(ConstraintId::from(reified_cut)),
329                            [Derivation::Rup(clause![], hints.collect())],
330                        )
331                        .into()],
332                    )?;
333                    cadical_veripb_tracer::CadicalCertCollector::new(
334                        &mut self.kernel.oracle,
335                        &proof_stuff.pt_handle,
336                    )
337                    .add_cert_clause(unit, unit_id)?;
338                } else {
339                    self.kernel.oracle.add_unit(block_lit)?;
340                }
341            }
342        }
343    }
344}
345
346impl<'learn, 'term, PBE, CE, ProofW, OInit, BCG> CoreBoost
347    for PMinimal<rustsat_cadical::CaDiCaL<'learn, 'term>, PBE, CE, ProofW, OInit, BCG>
348where
349    ProofW: io::Write + 'static,
350    (PBE, CE): MergeOllRef<PBE = PBE, CE = CE>,
351    OInit: Initialize<rustsat_cadical::CaDiCaL<'learn, 'term>>,
352{
353    fn core_boost(&mut self, opts: CoreBoostingOptions) -> MaybeTerminatedError<bool> {
354        ensure!(
355            self.kernel.stats.n_solve_calls == 0,
356            "cannot perform core boosting after solve has been called"
357        );
358        let Some(cb_res) = self.kernel.core_boost()? else {
359            return Done(false);
360        };
361        self.kernel.check_termination()?;
362        let reset_dbs = match &opts.after {
363            AfterCbOptions::Nothing => false,
364            AfterCbOptions::Reset => {
365                self.kernel.reset_oracle(true)?;
366                self.kernel.check_termination()?;
367                true
368            }
369            #[cfg(feature = "maxpre")]
370            AfterCbOptions::Inpro(techs) => {
371                self.obj_encs = self.kernel.inprocess(techs, cb_res)?;
372                self.kernel.check_termination()?;
373                return Done(true);
374            }
375        };
376        self.kernel.log_routine_start("merge encodings")?;
377        for (oidx, (reform, mut tot_db)) in cb_res.into_iter().enumerate() {
378            if reset_dbs {
379                debug_assert!(self.kernel.proof_stuff.is_none());
380                tot_db.reset_vars();
381            }
382            if !matches!(self.kernel.objs[oidx], Objective::Constant { .. }) {
383                if let Some(proofs::ProofStuff { pt_handle, .. }) = &self.kernel.proof_stuff {
384                    if !reform.reformulations.is_empty() {
385                        // delete remaining reformulation constraints from proof
386                        let proof = self.kernel.oracle.proof_tracer_mut(pt_handle).proof_mut();
387                        #[cfg(feature = "verbose-proofs")]
388                        proof.comment(&format_args!(
389                            "deleting remaining reformulation constraints from OLL of objective {oidx}"
390                        ))?;
391                        proof.delete_ids::<Var, Clause, _, _>(
392                            reform
393                                .reformulations
394                                .values()
395                                .map(|re| ConstraintId::from(re.proof_id.unwrap())),
396                            None,
397                        )?;
398                    }
399                }
400
401                self.obj_encs[oidx] = <(PBE, CE)>::merge(reform, tot_db, opts.rebase);
402            }
403            self.kernel.check_termination()?;
404        }
405        self.kernel.log_routine_end()?;
406        Done(true)
407    }
408}
409
410impl<'term, 'learn, ProofW, OInit, BCG>
411    Kernel<rustsat_cadical::CaDiCaL<'term, 'learn>, ProofW, OInit, BCG>
412where
413    ProofW: io::Write + 'static,
414{
415    /// Executes P-minimization from a cost and solution starting point
416    pub fn p_minimization(
417        &mut self,
418        mut costs: Vec<usize>,
419        mut solution: Assignment,
420        base_assumps: &[Lit],
421        obj_encs: &mut [ObjEncoding<GeneralizedTotalizer, Totalizer>],
422    ) -> MaybeTerminatedError<(
423        Vec<usize>,
424        Assignment,
425        Option<(Lit, Option<(AbsConstraintId, Vec<AbsConstraintId>)>)>,
426    )> {
427        debug_assert_eq!(costs.len(), self.stats.n_objs);
428        self.log_routine_start("p minimization")?;
429        let mut block_switch: Option<(Lit, Option<(AbsConstraintId, Vec<AbsConstraintId>)>)> = None;
430        let mut assumps = Vec::from(base_assumps);
431        #[cfg(feature = "coarse-convergence")]
432        let mut coarse = true;
433        loop {
434            #[cfg(feature = "coarse-convergence")]
435            let bound_costs: Vec<_> = costs
436                .iter()
437                .enumerate()
438                .map(|(_oidx, &c)| {
439                    if coarse {
440                        return obj_encs[_oidx].coarse_ub(c);
441                    }
442                    c
443                })
444                .collect();
445            assumps.drain(base_assumps.len()..);
446            // Block solutions dominated by the current one
447            if self.opts.enumeration == EnumOptions::NoEnum {
448                // Block permanently since no enumeration at Pareto point
449                let (block_clause, reification_ids) =
450                    self.dominated_block_clause(&costs, obj_encs)?;
451                if let Some(proof_stuff) = &mut self.proof_stuff {
452                    use rustsat::encodings::cert::CollectClauses;
453
454                    // this adds the "ideal cut"
455                    let cut_id = proofs::certify_pmin_cut(
456                        obj_encs,
457                        &self.objs,
458                        &costs,
459                        &solution,
460                        self.var_manager.max_enc_var(),
461                        proof_stuff,
462                        &mut self.oracle,
463                    )?;
464                    let proof = self
465                        .oracle
466                        .proof_tracer_mut(&proof_stuff.pt_handle)
467                        .proof_mut();
468                    // since there might be reifications of multiple assumptions per one encoding
469                    // involved, the actual clause might differ and is added as rup here
470                    let clause_id = proof.reverse_unit_prop(
471                        &block_clause,
472                        reification_ids
473                            .into_iter()
474                            .chain([cut_id])
475                            .map(ConstraintId::from),
476                    )?;
477                    let mut collector = cadical_veripb_tracer::CadicalCertCollector::new(
478                        &mut self.oracle,
479                        &proof_stuff.pt_handle,
480                    );
481                    collector.add_cert_clause(block_clause, clause_id)?;
482                } else {
483                    self.oracle.add_clause(block_clause)?;
484                }
485            } else {
486                // Permanently block last cadidate
487                if let Some((block_lit, ids)) = block_switch {
488                    if let Some(proof_stuff) = &mut self.proof_stuff {
489                        use pigeons::{ConstraintId, Derivation, ProofGoal, ProofGoalId};
490                        use rustsat::encodings::cert::CollectClauses;
491
492                        let (reified_cut, reified_assump_ids) = ids.unwrap();
493                        let id = proofs::certify_pmin_cut(
494                            obj_encs,
495                            &self.objs,
496                            &costs,
497                            &solution,
498                            self.var_manager.max_enc_var(),
499                            proof_stuff,
500                            &mut self.oracle,
501                        )?;
502                        let proof = self
503                            .oracle
504                            .proof_tracer_mut(&proof_stuff.pt_handle)
505                            .proof_mut();
506                        let hints = [ConstraintId::last(2), ConstraintId::last(1), id.into()]
507                            .into_iter()
508                            .chain(reified_assump_ids.into_iter().map(ConstraintId::from));
509                        let unit = clause![block_lit];
510                        let unit_id = proof.redundant(
511                            &unit,
512                            [],
513                            [ProofGoal::new(
514                                ProofGoalId::from(ConstraintId::from(reified_cut)),
515                                [Derivation::Rup(clause![], hints.collect())],
516                            )
517                            .into()],
518                        )?;
519                        cadical_veripb_tracer::CadicalCertCollector::new(
520                            &mut self.oracle,
521                            &proof_stuff.pt_handle,
522                        )
523                        .add_cert_clause(unit, unit_id)?;
524                    } else {
525                        self.oracle.add_unit(block_lit)?;
526                    }
527                }
528                // Temporarily block to allow for enumeration at Pareto point
529                let block_info = self.tmp_block_dominated(&costs, obj_encs)?;
530                let blit = block_info.0;
531                block_switch = Some(block_info);
532                assumps.push(blit);
533            }
534            // Force next solution to dominate the current one
535            #[cfg(not(feature = "coarse-convergence"))]
536            assumps.extend(self.enforce_dominating(&costs, obj_encs)?);
537            #[cfg(feature = "coarse-convergence")]
538            assumps.extend(self.enforce_dominating_cert(&bound_costs, obj_encs)?);
539
540            // Check if dominating solution exists
541            let res = self.solve_assumps(&assumps)?;
542            if res == SolverResult::Unsat {
543                #[cfg(feature = "coarse-convergence")]
544                if bound_costs != costs {
545                    // Switch to fine convergence
546                    coarse = false;
547                    continue;
548                }
549                self.log_routine_end()?;
550                // Termination criteria, return last solution and costs
551                return Done((costs, solution, block_switch));
552            }
553            self.check_termination()?;
554
555            (costs, solution) = self.get_solution_and_internal_costs(
556                self.opts
557                    .heuristic_improvements
558                    .solution_tightening
559                    .wanted(Phase::Minimization),
560            )?;
561            self.log_candidate(&costs, Phase::Minimization)?;
562            self.check_termination()?;
563            self.phase_solution(solution.clone())?;
564        }
565    }
566
567    /// Gets assumptions to enforce that the next solution dominates the given
568    /// cost point.
569    pub fn enforce_dominating<'a>(
570        &'a mut self,
571        costs: &'a [usize],
572        obj_encs: &'a mut [ObjEncoding<GeneralizedTotalizer, Totalizer>],
573    ) -> anyhow::Result<impl Iterator<Item = Lit> + 'a> {
574        debug_assert_eq!(costs.len(), self.stats.n_objs);
575        if let Some(proofs::ProofStuff { pt_handle, .. }) = &self.proof_stuff {
576            let proof: *mut _ = self.oracle.proof_tracer_mut(pt_handle).proof_mut();
577            #[cfg(feature = "verbose-proofs")]
578            {
579                use itertools::Itertools;
580                let proof = unsafe { &mut *proof };
581                proof.comment(&format_args!(
582                    "building assumptions to dominate [{}]",
583                    costs.iter().format(", ")
584                ))?;
585            }
586            let mut collector = CadicalCertCollector::new(&mut self.oracle, pt_handle);
587            for (idx, &cst) in costs.iter().enumerate() {
588                let enc = &mut obj_encs[idx];
589                enc.encode_ub_change_cert(
590                    cst..cst + 1,
591                    &mut collector,
592                    &mut self.var_manager,
593                    unsafe { &mut *proof },
594                )?;
595            }
596        } else {
597            for (idx, &cst) in costs.iter().enumerate() {
598                let enc = &mut obj_encs[idx];
599                enc.encode_ub_change(cst..cst + 1, &mut self.oracle, &mut self.var_manager)?;
600            }
601        }
602        Ok(costs.iter().enumerate().flat_map(|(idx, &cst)| {
603            let enc = &mut obj_encs[idx];
604            enc.enforce_ub(cst).unwrap().into_iter()
605        }))
606    }
607
608    /// Gets a clause blocking solutions (weakly) dominated by the given cost point,
609    /// given objective encodings.
610    pub fn dominated_block_clause(
611        &mut self,
612        costs: &[usize],
613        obj_encs: &mut [ObjEncoding<GeneralizedTotalizer, Totalizer>],
614    ) -> anyhow::Result<(Clause, Vec<AbsConstraintId>)> {
615        debug_assert_eq!(costs.len(), obj_encs.len());
616        let mut reification_ids = Vec::new();
617        let mut clause = Clause::default();
618        for (idx, &cst) in costs.iter().enumerate() {
619            // Don't block
620            if cst <= obj_encs[idx].offset() {
621                continue;
622            }
623            let enc = &mut obj_encs[idx];
624            if matches!(enc, ObjEncoding::Constant) {
625                continue;
626            }
627            // Encode and add to solver
628            self.extend_encoding(enc, cst - 1..cst)?;
629            let assumps = enc.enforce_ub(cst - 1)?;
630            if assumps.len() == 1 {
631                clause.add(assumps[0]);
632            } else {
633                debug_assert!(!assumps.is_empty());
634                let and_lit = self.var_manager.new_var().pos_lit();
635                if let Some(proof_stuff) = &mut self.proof_stuff {
636                    let only_if_def = proofs::certify_assump_reification(
637                        &mut self.oracle,
638                        proof_stuff,
639                        &self.objs[idx],
640                        enc,
641                        cst,
642                        and_lit,
643                        &assumps,
644                    )?;
645                    reification_ids.push(only_if_def);
646                } else {
647                    for cl in atomics::lit_impl_cube(and_lit, &assumps) {
648                        self.oracle.add_clause(cl)?;
649                    }
650                }
651                clause.add(and_lit)
652            }
653        }
654        Ok((clause, reification_ids))
655    }
656
657    /// Temporarily blocks solutions dominated by the given cost point. Returns
658    /// and assumption that needs to be enforced in order for the blocking to be
659    /// enforced.
660    pub fn tmp_block_dominated(
661        &mut self,
662        costs: &[usize],
663        obj_encs: &mut [ObjEncoding<GeneralizedTotalizer, Totalizer>],
664    ) -> anyhow::Result<(Lit, Option<(AbsConstraintId, Vec<AbsConstraintId>)>)> {
665        use pigeons::VarLike;
666
667        debug_assert_eq!(costs.len(), self.stats.n_objs);
668        let (mut clause, reification_ids) = self.dominated_block_clause(costs, obj_encs)?;
669        let block_lit = self.var_manager.new_var().pos_lit();
670        clause.add(block_lit);
671        self.oracle.add_clause_ref(&clause).unwrap();
672        if let Some(proofs::ProofStuff { pt_handle, .. }) = &self.proof_stuff {
673            let proof = self.oracle.proof_tracer_mut(pt_handle).proof_mut();
674            let id = proof.redundant(&clause, [block_lit.var().substitute_fixed(true)], None)?;
675            Ok((!block_lit, Some((id, reification_ids))))
676        } else {
677            Ok((!block_lit, None))
678        }
679    }
680}