Skip to content

feat: Make Literal wrapper of Predicate#398

Draft
ImkoMarijnissen wants to merge 15 commits intomainfrom
feat/literal-atomic
Draft

feat: Make Literal wrapper of Predicate#398
ImkoMarijnissen wants to merge 15 commits intomainfrom
feat/literal-atomic

Conversation

@ImkoMarijnissen
Copy link
Copy Markdown
Contributor

@ImkoMarijnissen ImkoMarijnissen commented Mar 26, 2026

Related to #375.

This PR aims to move away from having to create a fresh variable for each Literal by making it a wrapper around a Predicate.

Any feedback on this PR is welcome!

TODOs

  • Figure out how to post bool2int and the boolean equals constraint since it has a list of Literals as input, which sum up to a DomainId, which means that the scaling trick does not work.
  • I have created a system for tracking when a literal is tracked, in which case the event should use notify instead of notify_predicate.
  • As discussed in Preprocessing variables #375, backtracking is somewhat tricky; the problem currently is that for a literal, you might now be updated multiple times (since it could occur that the predicate notification notifies of the same Predicate multiple times). This breaks some of the invariants that we have for certain propagators, so these should be double-checked.
  • Fix issues in the python interface from to_integer_variable being removed.
  • Use trail for keeping track of the backtrack notification events for literals

@ImkoMarijnissen ImkoMarijnissen changed the title feat: Make Literal wrapped of Predicate feat: Make Literal wrapper of Predicate Mar 26, 2026
@EmirDe
Copy link
Copy Markdown
Contributor

EmirDe commented Mar 26, 2026

It might be a good idea to briefly describe what has been done, the main idea, and the main change. It would make reviewing easier.

@maartenflippo
Copy link
Copy Markdown
Contributor

A few thoughts on this:

  1. bool2int at the moment is only posted when doing logging a proof with inferences. Otherwise, we use the domain underlying the 0-1 literal as the integer domain. See merge_equivalences.rs for this logic.
  2. Now we have a watchlist for predicates with a local ID (i.e. those that emulate an integer variable) and a watchlist for predicate IDs without a local ID. Perhaps we can merge those? That would mean every predicate receives a local ID. But then the notifier does not have two watch lists for predicates

Copy link
Copy Markdown
Contributor

@maartenflippo maartenflippo left a comment

Choose a reason for hiding this comment

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

As far as I can tell, this feature is not extensively tested. The test cases with bool2int are not sufficient, since they always have the literal be over a 0-1 variable.

Comment on lines +77 to +81
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum IntegerVariableEnum {
DomainId(AffineView<DomainId>),
Literal(AffineView<Literal>),
}
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.

Can we move this to pumpkin-solver instead? I don't think we need to pollute the core with this?

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.

Keep in core, but move to different file

Comment on lines +77 to +81
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum IntegerVariableEnum {
DomainId(AffineView<DomainId>),
Literal(AffineView<Literal>),
}
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.

Can we rename this? We never suffix the type with what kind of type it is. I guess this is done to avoid name clashes with the trait. Perhaps we can call it AnyInteger?

Comment on lines +77 to +81
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum IntegerVariableEnum {
DomainId(AffineView<DomainId>),
Literal(AffineView<Literal>),
}
Copy link
Copy Markdown
Contributor

@maartenflippo maartenflippo Apr 9, 2026

Choose a reason for hiding this comment

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

Why put the affine view inside the variants? We could unwrap that and let any user wrap the enum with an affine view

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.

At this point, what is the reason for keeping Literal? We can do without it I think? We can simply implement IntegerVariable for Predicate?

Comment on lines +12 to +20
#[derive(Debug, Default, Clone)]
pub(crate) struct PredicateWatchList {
/// The watch list from predicates to propagators.
pub(crate) watch_list_predicate_id: KeyedVec<PredicateId, Vec<PropagatorId>>,
// TODO: Should use direct hashing
pub(crate) literal_watch_list:
HashMap<Literal, HashMap<PropagatorId, (LocalId, EnumSet<DomainEvent>)>>,
pub(crate) literal_watch_list_backtrack:
HashMap<Literal, HashMap<PropagatorId, (LocalId, EnumSet<DomainEvent>)>>,
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.

Conceptually, why would we have predicates with and without a local ID? Can they be watched as both? Would it not be cleaner to say that a notification happens to a LocalId, removing the need for notify_predicate?

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.

After a discussion we decided to keep this distinction for now. It may make sense to simplify this in the future to always associate any registration with a local ID, but it is not clear that goes without problems

Comment on lines +47 to 55
pub fn scaled(&self, scale: i32) -> IntExpression {
let int_expr: IntExpression = (*self).into();
int_expr.scaled(scale)
}

pub fn offset(&self, offset: i32) -> IntExpression {
let int_expr: IntExpression = (*self).into();
int_expr.offset(offset)
}
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.

This should not happen. A BoolExpression should be just a predicate/literal

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.

3 participants