-
Notifications
You must be signed in to change notification settings - Fork 21
feat: Impement State API #319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| impl From<Conflict> for Inconsistency { | ||
| fn from(value: Conflict) -> Self { | ||
| match value { | ||
| Conflict::Propagator(propagator_conflict) => { | ||
| Inconsistency::Conflict(propagator_conflict) | ||
| } | ||
| Conflict::EmptyDomain(_) => Inconsistency::EmptyDomain, | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this necessary? It seems counterintuitive as Inconsistency is more toward the core than Conflict .
| /// A conflict because a domain became empty. | ||
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
| pub(crate) struct EmptyDomainConflict { | ||
| pub struct EmptyDomainConflict { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can move this alongside the defintion of Conflict
| #[cfg(test)] | ||
| pub(crate) fn next_key(&self) -> Key { | ||
| Key::create_from_index(self.len()) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change in test solver to generate a new key
| pub(crate) fn new() -> Self { | ||
| let mut result = Self { | ||
| assignments: Default::default(), | ||
| trailed_values: TrailedValues::default(), | ||
| variable_names: VariableNames::default(), | ||
| propagator_queue: PropagatorQueue::default(), | ||
| propagators: PropagatorStore::default(), | ||
| reason_store: ReasonStore::default(), | ||
| notification_engine: NotificationEngine::default(), | ||
| inference_codes: None, | ||
| statistics: StateStatistics::default(), | ||
| }; | ||
| // As a convention, the assignments contain a dummy domain_id=0, which represents a 0-1 | ||
| // variable that is assigned to one. We use it to represent predicates that are | ||
| // trivially true. We need to adjust other data structures to take this into account. | ||
| let dummy_id = Predicate::trivially_true().get_domain(); | ||
|
|
||
| result | ||
| .variable_names | ||
| .add_integer(dummy_id, "Dummy".to_owned()); | ||
| assert!(dummy_id.id() == 0); | ||
| assert!(result.assignments.get_lower_bound(dummy_id) == 1); | ||
| assert!(result.assignments.get_upper_bound(dummy_id) == 1); | ||
|
|
||
| result | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be a Default::default() implementation
| /// Component responsible for providing notifications for changes to the domains of variables | ||
| /// and/or the polarity [Predicate]s | ||
| pub(crate) notification_engine: NotificationEngine, | ||
| pub(crate) inference_codes: Option<KeyedVec<InferenceCode, (ConstraintTag, Arc<str>)>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for this to be an Option
| /// let mut state = State::default(); | ||
| /// let variable = state.new_interval_variable(1, 10, Some("x1".to_string())); | ||
| /// | ||
| /// state.new_checkpoint(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's clarify the interaction with get_decision_level() and add an assert that the DL equals 1.
| /// This method should only be called when an empty domain is encountered during propagation. | ||
| /// It removes the final trail element and returns the [`Conflict`]. | ||
| pub(crate) fn prepare_for_conflict_resolution(&mut self) -> Conflict { | ||
| // TODO: As a temporary solution, we remove the last trail element. | ||
| // This way we guarantee that the assignment is consistent, which is needed | ||
| // for the conflict analysis data structures. The proper alternative would | ||
| // be to forbid the assignments from getting into an inconsistent state. | ||
| let (trigger_predicate, trigger_reason, trigger_inference_code) = | ||
| self.assignments.remove_last_trail_element(); | ||
|
|
||
| Conflict::EmptyDomain(EmptyDomainConflict { | ||
| trigger_predicate, | ||
| trigger_reason, | ||
| trigger_inference_code, | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be pub(crate)? It seems like we are leaking implementation details unnecessarily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do the prepare_for_confict when posting predicates in PropagationContextMut
| /// Performs a single call to [`Propagator::propagate`] for the propagator with the provided | ||
| /// [`PropagatorId`]. | ||
| /// | ||
| /// Other propagators could be enqueued as a result of the changes made by the propagated | ||
| /// propagator but a call to [`State::fixed_point_propagate`] is | ||
| /// required for further propagation to occur. | ||
| /// | ||
| /// It could be that the current [`State`] implies a conflict by propagation. In that case, an | ||
| /// [`Err`] with [`Conflict`] is returned. | ||
| /// | ||
| /// Once the [`State`] is conflicting, then the only operation that is defined is | ||
| /// [`State::restore_to`]. All other operations and queries on the state are undetermined. | ||
| pub(crate) fn propagate(&mut self, propagator_id: PropagatorId) -> Result<(), Conflict> { | ||
| self.statistics.num_propagators_called += 1; | ||
|
|
||
| let num_trail_entries_before = self.assignments.num_trail_entries(); | ||
|
|
||
| let propagation_status = { | ||
| let propagator = &mut self.propagators[propagator_id]; | ||
| let context = PropagationContextMut::new( | ||
| &mut self.trailed_values, | ||
| &mut self.assignments, | ||
| &mut self.reason_store, | ||
| &mut self.notification_engine, | ||
| propagator_id, | ||
| ); | ||
| propagator.propagate(context) | ||
| }; | ||
|
|
||
| match propagation_status { | ||
| Ok(_) => { | ||
| // Notify other propagators of the propagations and continue. | ||
| self.notification_engine | ||
| .notify_propagators_about_domain_events( | ||
| &mut self.assignments, | ||
| &mut self.trailed_values, | ||
| &mut self.propagators, | ||
| PropagatorHandle { | ||
| id: PropagatorId(0), | ||
| propagator: std::marker::PhantomData, | ||
| }, | ||
| &mut self.propagator_queue, | ||
| ); | ||
| } | ||
| Err(inconsistency) => { | ||
| self.statistics.num_conflicts += 1; | ||
| match inconsistency { | ||
| // A propagator did a change that resulted in an empty domain. | ||
| Inconsistency::EmptyDomain => { | ||
| let info = self.prepare_for_conflict_resolution(); | ||
| return Err(info); | ||
| } | ||
| // A propagator-specific reason for the current conflict. | ||
| Inconsistency::Conflict(conflict) => { | ||
| pumpkin_assert_advanced!(DebugHelper::debug_reported_failure( | ||
| &self.trailed_values, | ||
| &self.assignments, | ||
| &conflict.conjunction, | ||
| &self.propagators[propagator_id], | ||
| propagator_id, | ||
| &self.notification_engine | ||
| )); | ||
|
|
||
| let stored_conflict_info = Conflict::Propagator(conflict); | ||
| return Err(stored_conflict_info); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| pumpkin_assert_extreme!( | ||
| DebugHelper::debug_check_propagations( | ||
| num_trail_entries_before, | ||
| propagator_id, | ||
| &self.trailed_values, | ||
| &self.assignments, | ||
| &mut self.reason_store, | ||
| &mut self.propagators, | ||
| &self.notification_engine | ||
| ), | ||
| "Checking the propagations performed by the propagator led to inconsistencies!" | ||
| ); | ||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't we discuss this was unnecessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can indeed be private
| pub fn get_propagation_reason( | ||
| &mut self, | ||
| predicate: Predicate, | ||
| reason_buffer: &mut (impl Extend<Predicate> + AsRef<[Predicate]>), | ||
| ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should return the inference code if the explanation comes from a propagator. Otherwise proof logging cannot easily extract that information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return Option of inference code when it is on the trail; use the get reason method in State in conflict analysis context
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I now return the trail position; one problem is that the ExplanationContext takes as input a CurrentNogood, I have now added this to the interface
| pub(crate) id: PropagatorId, | ||
| pub(crate) propagator: PhantomData<P>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't be public at all
…t return to a consistent state
Review re-requested
In our efforts to modularise the solver, this PR implements the extraction of
Statefrom the solver. This structure contains the variables and the propagators (in addition to the notification engine, and the propagation queue).TODO