Skip to content

feat(pumpkin-core): Introduce runtime consistency verification of propagators#374

Open
maartenflippo wants to merge 16 commits intomainfrom
feat/propagation-checkers
Open

feat(pumpkin-core): Introduce runtime consistency verification of propagators#374
maartenflippo wants to merge 16 commits intomainfrom
feat/propagation-checkers

Conversation

@maartenflippo
Copy link
Contributor

@maartenflippo maartenflippo commented Feb 27, 2026

To increase the trust in propagator implementations, we complement the existing inference checkers with consistency checkers. A consistency checker verifies that a propagator is at its intended consistency level.

The function PropagatorConstructor::add_inference_checkers now returns a ConsistencyChecker implementation. This means there is one ConsistencyChecker per propagator. The interface forces propagator implementations to return a consistency checker if they want to run any checkers at all.

Since the add_inference_checkers had a default implementation, that now returns a consistency checker that always indicates the desired consistency level is achieved. For any new propagators, we do not want to expose that, so the implementation is marked deprecated and is hidden from the documentation. However, it reduces the size of this already significant PR as we don't have to implement consistency checkers for every propagator as well.

In this PR, we also implement reusable checkers for:

  • Bound consistency
  • Domain consistency

TODO

  • Witness generators for bound consistent propagators

    • Linear
    • Maximum
  • Witness generators for domain consistent propagators

    • Binary equals
    • (Binary) Not equals
  • Migrate propagate_from_scratch to custom consistency checkers where bounds or domain consistency is not applicable

  • Witness generators for non-bound consistent propagators

  • Division (Not bound-consistent since it only propagates when the denominator spans 0)

  • Multiplication (Only propagates in certain situations)


fn assign(&self, value: i32) -> SingleVariableAssignment {
assert_eq!((value - self.offset) % self.scale, 0);
let inner_assignment = (value - self.offset) / self.scale;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not use the invert method here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I want to explicitly maintain that no rounding happens.

Comment on lines +424 to +439
#[cfg(feature = "check-propagations")]
{
use crate::propagation::checkers::ConsistencyChecker;

#[allow(trivial_casts, reason = "removing it causes a compiler error")]
let previous_checker = self.consistency_checkers.insert(
handle.propagator_id(),
BoxedConsistencyChecker::from(
Box::new(consistency_checker) as Box<dyn ConsistencyChecker>
),
);
assert!(
previous_checker.is_none(),
"somehow adding multiple consistency checkers to the same propagator"
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not add this directly in constructor.add_inference? It seems like this is doing the same thing as for the InferenceChecker

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this does not work due to our strong/weak consistency difference. We should discuss on Zoom at some point

Copy link
Contributor

Choose a reason for hiding this comment

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

In my mind, it would be good to explicitly differentiate between InferenceChecker and ConsistencyChecker; naming the module checkers makes this ambiguous

Domain,
}

fn assert_consistency<PropagationChecker>(
Copy link
Contributor

Choose a reason for hiding this comment

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

As per usual, documentation please

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Outdated

}
}

fn support_value<PropagationChecker>(
Copy link
Contributor

Choose a reason for hiding this comment

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

Docs please

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Outdated

Comment on lines +23 to +34
#[derive(Clone, Debug, Default)]
pub struct ScopeBuilder(Vec<(LocalId, DomainId)>);

impl ScopeBuilder {
pub fn add(&mut self, local_id: LocalId, domain_id: DomainId) {
self.0.push((local_id, domain_id));
}

pub fn build(self) -> Scope {
Scope(self.0)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the purpose of the ScopeBuilder? This seems to be a pass-along struct at the moment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed. Removed

let last_idx = hypercube_predicates.len() - 1;
[
context.register_predicate(hypercube_predicates[0.min(last_idx)]),
context.register_predicate(hypercube_predicates[0]),
Copy link
Contributor

Choose a reason for hiding this comment

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

?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a new nightly clippy warning, so its fixed to get the code style action to pass

@maartenflippo maartenflippo force-pushed the feat/propagation-checkers branch from 04d580a to 9a528c3 Compare March 3, 2026 04:52
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.

2 participants