Skip to content

feat: Semantic Minimisation during Conflict Analysis#406

Open
ImkoMarijnissen wants to merge 59 commits into
mainfrom
feat/minimisation-during-ca
Open

feat: Semantic Minimisation during Conflict Analysis#406
ImkoMarijnissen wants to merge 59 commits into
mainfrom
feat/minimisation-during-ca

Conversation

@ImkoMarijnissen

@ImkoMarijnissen ImkoMarijnissen commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Closes #397.

After discussing with @maartenflippo, I implemented a very simple implementation of the conjoin function from "Semantic Learning for Lazy Clause Generation - Feydy et al.".

Some anecdotal examples of the effect:
For an RCPSP instance (01.dzn), the number of failures goes from 3,711 to 3,376 and for another instance (J120_60_5), the number of failures to get to a solution of 108 is reduced from 28,303 to 27,855. For foxgeesecorn, the number of failures to get to an objective of 32 goes from 114,811 to 81,268.

There are a few things which are not entirely clear to me:

  • Currently, whenever replacing a predicate with a fully new one (e.g., we replace $[x \geq 5]$ and $[x \neq 5]$ with $[x \geq 6]$), then I do a check that it will not be resolved on next to avoid infinite loops. Is this the correct way to handle it or is something else going wrong?
  • In the paper, the conjoin function is underspecified, and it is somewhat unclear what exactly it does; for now, we assume that the elements in the working nogood are in the left side of the rewrite rules, and the added propagation reasons are on the right side of the rewrite rules. It was unclear to us what the reason for this is, but it appears to create incorrect nogoods otherwise.
  • I have removed the check that a predicate has not already been seen. We think that this led to incorrectness but it would be good to double-check.

TODOs

  • Implement more efficiently, likely with a data structure which is similar to the one used for semantic minimisation.
  • Do not log all initial bounds but only when they are used
  • Profile on larger benchmark set

Experimentation

In general, it appears to significantly improve the number of instances for which a solution is found at the cost of proving optimality on instances. This could be due to the fact that there are 24 OOM errors compared to the 42 OOM errors in main. The MiniZinc score is also improved (even when filtering out instances where an error occurred; i.e., OOMs) quite significantly. This could be due to the fact that the average primal integral also appears to be significantly reduced.

Overall Results

main

{'ERROR': 47,
 'OPTIMAL': 104,
 'SATISFIABLE': 141,
 'UNKNOWN': 96,
 'UNSATISFIABLE': 5}

new

{'ERROR': 29,
 'OPTIMAL': 101,
 'SATISFIABLE': 156,
 'UNKNOWN': 102,
 'UNSATISFIABLE': 5}

MiniZinc Score

{
    'main': 119.32,
    'new': 148.68
}

Average Primal Integral

{
    'main': 49.45,
    'new': 29.55
}

maartenflippo and others added 30 commits March 29, 2026 12:45
Comment on lines +337 to +345
while lower_bound != i32::MIN
&& let Some((lb_updated, _)) = self.domains[domain].holes.get(&(lower_bound - 1))
&& *lb_updated
&& context.get_checkpoint_for_predicate(predicate!(domain != lower_bound - 1))
== Some(0)
{
lower_bound -= 1;
context.explain_root_assignment(predicate!(domain != lower_bound));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand this correctly, it introduces $[x \neq v]$ for the initial holes. Why not just iterate the holes? Also note that if we introduce $\top \rightarrow [x \ge 5]$, then there is no need to introduce $\top \rightarrow [x \ne v]$ for $v < 5$.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only introduces the hole(s) when we have an element $[x \geq v]$ in the nogood, and a sequence of holes caused the lower-bound to become $v'$ (such that $v'$ > $v$)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I am not 100% sure I understand what you mean. Could you give a concrete example?

@ImkoMarijnissen
ImkoMarijnissen marked this pull request as ready for review July 15, 2026 08:36
@ImkoMarijnissen

Copy link
Copy Markdown
Contributor Author

Can we remove semantic minimisation in extract_final_nogood?

@maartenflippo maartenflippo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry :). More changes

Atomic: AtomicConstraint,
{
pub fn reset_domain(&mut self, identified: Atomic::Identifier) {
let _ = self.domains.insert(identified, Domain::all_integers());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the domain is not present, let's not insert anything

Comment on lines +174 to +181
/// Decides whether to apply semantic minimisation during conflict analysis; according to the
/// idea proposed in "Semantic Learning for Lazy Clause Generation - Feydy et al. (2013)".
///
/// If this flag is present then the minimisation is turned on.
///
/// Possible values: bool
#[arg(long = "iterative-minimisation", verbatim_doc_comment)]
iterative_minimisation: bool,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be true by default?

Comment on lines +47 to +52
#[derive(Clone, Debug)]
struct IterativeDomain {
lb: i32,
ub: i32,
holes: Vec<i32>,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the VariableState?

There is probably a reason why this is necessary. In that case, please document it

impl Default for ResolutionResolver {
fn default() -> Self {
ResolutionResolver::new(AnalysisMode::OneUIP, true)
ResolutionResolver::new(AnalysisMode::OneUIP, true, false)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the default be true?

Comment on lines +184 to +185
// println!("======================================================================");
// println!("C: {conflict_nogood:?}");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Comment on lines +216 to 228
// println!(
// "WORKING NOGOOD: {:?}",
// self.processed_nogood_predicates
// .iter()
// .copied()
// .chain(
// self.to_process_heap
// .keys()
// .map(|id| self.predicate_id_generator.get_predicate(id))
// )
// .collect::<Vec<_>>()
// );
// Replace the predicate from the nogood that has been assigned last on the trail.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented code

Comment on lines +251 to +252
// println!("R: {:?} -> {next_predicate}", self.reason_buffer);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

});
// Ignore root level predicates.
if dec_level == 0 {
// println!("{predicate:?} ROOT LEVEL");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

Comment on lines +649 to +660
// println!(
// "FINAL NOGOOD: {:?}",
// self.processed_nogood_predicates
// .iter()
// .copied()
// .chain(
// self.to_process_heap
// .keys()
// .map(|id| self.predicate_id_generator.get_predicate(id))
// )
// .collect::<Vec<_>>()
// );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Revisit Semantic Minimisation during Learning

3 participants