Skip to content

Add CP code base#482

Draft
alexzucca90 wants to merge 20 commits intodwavesystems:mainfrom
alexzucca90:dev/cp
Draft

Add CP code base#482
alexzucca90 wants to merge 20 commits intodwavesystems:mainfrom
alexzucca90:dev/cp

Conversation

@alexzucca90
Copy link
Collaborator

Basic structure for integrating domains and filtering routines.

@alexzucca90 alexzucca90 marked this pull request as draft February 27, 2026 23:53
#pragma once
#include "dwave-optimization/cp/core/index_transform.hpp"
#include "dwave-optimization/cp/core/propagator.hpp"
namespace dwave::optimization::cp {
Copy link
Member

Choose a reason for hiding this comment

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

Minor but I would prefer constraint_propagation or similar as a "top-level" name instead of cp. Abbreviating it on less public interfaces or within methods is fine.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

to be precise you mean

namespace dwave::optimization::constraint_propagation {

?

Copy link
Member

Choose a reason for hiding this comment

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

Yes. I could be convinced otherwise though!

#include "dwave-optimization/cp/core/propagator.hpp"
namespace dwave::optimization::cp {

/// Utility class to help schedule propagators when variable change their domains
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Utility class to help schedule propagators when variable change their domains
/// Utility class to help schedule propagators when variables change their domains

/// - map variable index to propagator
class Advisor {
public:
Advisor(Propagator* p, ssize_t p_input, std::unique_ptr<IndexTransform> index_transform);
Copy link
Member

Choose a reason for hiding this comment

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

p_input is the topological index of a node?

Copy link
Member

Choose a reason for hiding this comment

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

Actually it seems like it is not used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch. I added it in case some propagators need to know which variable triggered the propagator. p_input is which input of that propagator the variable is. I'd wait a bit more to remove it for now, just in case I need it soon.

Copy link
Member

Choose a reason for hiding this comment

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

Re-explaining this to make sure I get it: each propagator is associated with two or more variables (i.e. CPVars). When the propagator is called, it wants to be efficient in terms of which of its "output" indices need to be re-evaluated (e.g. shrinking bounds of an output index, if it's a bound consistency propagator). This is the role of the Advisor: each propagator adds an Advisor to each of its associated variables, and that Advisor owns an IndexTransform which translates indices of the associated variable to change output indices.

However, it also seems reasonable that a propagator may want to know not just which of its output indices needs to be re-evaluated, but also which of the associated variables that change came from. So you are allowing for that info to be passed through here.

Now correct where I went wrong 😆

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I missed this comment but you are correct!

ssize_t num_propagators() const { return propagators_.size(); }

protected:
bool locked_ = false;
Copy link
Member

Choose a reason for hiding this comment

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

Seems like locked_ is never set by anything? Am I missing a lock() method somewhere?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good catch again, haven't worked on that yet.
And sorry for my work being somewhat nonlinear, I add stuff that I think I need but I don't complete until later..

Copy link
Member

Choose a reason for hiding this comment

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

No worries. Is the idea here to lock the CP model after parsing the dwopt model, and then we can be sure that the model is not modified during the run?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes, that's right!

virtual ~CPVar() = default;

// constructor
CPVar(const CPModel& model, const dwave::optimization::ArrayNode* node_ptr, int index);
Copy link
Member

Choose a reason for hiding this comment

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

This index is the topological index of the node_ptr, correct? Why not just derive it from the node_ptr directly in the implementation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good point!


ssize_t max_size, min_size;

if (node_->size() > 0) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (node_->size() > 0) {
if (not node_->dynamic()) {

You also want to handle size 0 arrays here, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah good catch!

assert(this->cp_var_index_ >= 0);
assert(static_cast<ssize_t>(state.var_state_.size()) > cp_var_index_);

// TODO: for now we don't handle dynamically sized nodes
Copy link
Member

Choose a reason for hiding this comment

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

Seems like this does handle dynamic arrays now, as long as they have bounds on size

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oh yeah, obsolete comment!

throw std::invalid_argument("Array has no max size available");
}

if (not sizeinfo.min.has_value()) {
Copy link
Member

Choose a reason for hiding this comment

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

It should be safe to substitute zero in here, though that may not be desirable

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good point, I want to check again how sizeinfo works and I'll likely follow your suggestion


if (node_->size() > 0) {
// Static array
max_size = node_->size();
Copy link
Member

Choose a reason for hiding this comment

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

You could use sizeinfo.min/max for these and simplify the logic I think

namespace dwave::optimization::cp {
class DomainListener {
public:
virtual ~DomainListener() = default;
Copy link
Member

Choose a reason for hiding this comment

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

I think if you're defining a virtual destructor like this, you should also declare the copy and move constructors to follow the rule of three, probably as = delete to prevent object slicing.

The plan is to have other types of listeners (other than CPVar::listener) at some point right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The point is that if there are other variables types (other than CPVar) then I may need other types of listeners. But to decouple domains from variables, the domains respond to this interface to trigger propagators.

Anyways, yes I need to follow the rule of three! I'll add that!

/// Return the minimum size of the array
virtual ssize_t min_size() const = 0;

/// Return the minimum size of the array
Copy link
Contributor

Choose a reason for hiding this comment

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

maximum

Comment on lines +29 to +30
IntervalArray(StateManager* sm, ssize_t min_size, ssize_t max_size, double lb, double up);
IntervalArray(StateManager* sm, ssize_t size, double lb, double up);
Copy link
Contributor

@fastbodin fastbodin Mar 11, 2026

Choose a reason for hiding this comment

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

I think you mean double ub here?

Comment on lines +56 to +57
// Change double do an object that can be backtracked.
// And maybe get
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Change double do an object that can be backtracked.
// And maybe get

Looks like an old TODO that has been addressed?

Comment on lines +40 to +41
/// Return the size of the domain of the index-th variable
virtual double size(int index) const = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

I might prefer to call this "width" (consistent with the width of a real-valued interval) so that the term "size" is not overloaded so much

Copy link
Contributor

Choose a reason for hiding this comment

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

hmm, though if we support sparse set domains, something like "cardinality" might be better, though that would not be accurate for a real-valued interval

Add basic binary ops and reduce
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.

4 participants