-
Notifications
You must be signed in to change notification settings - Fork 32
Add CP code base #482
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?
Add CP code base #482
Changes from all commits
dc9ff26
ef5127a
612a069
bca5f13
8ef6bc7
3e1fa42
abff5d1
9e0de2b
2d5cf5f
baacb91
aae8673
1b42107
42aa8b4
aa8e594
c6794eb
07e4afb
9f24340
dafc3f0
b3bcf72
f0ed0e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,45 @@ | ||||||
| // Copyright 2026 D-Wave Systems Inc. | ||||||
| // | ||||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
| // you may not use this file except in compliance with the License. | ||||||
| // You may obtain a copy of the License at | ||||||
| // | ||||||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||||||
| // | ||||||
| // Unless required by applicable law or agreed to in writing, software | ||||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
| // See the License for the specific language governing permissions and | ||||||
| // limitations under the License. | ||||||
|
|
||||||
| #pragma once | ||||||
| #include "dwave-optimization/cp/core/index_transform.hpp" | ||||||
| #include "dwave-optimization/cp/core/propagator.hpp" | ||||||
| namespace dwave::optimization::cp { | ||||||
|
|
||||||
| /// Utility class to help schedule propagators when variable change their domains | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| /// In our settings variables and constraints are arrays, so we need a way to: | ||||||
| /// - trigger propagator indices when variable indices' domains change | ||||||
| /// - map variable index to propagator | ||||||
| class Advisor { | ||||||
| public: | ||||||
| Advisor(Propagator* p, ssize_t p_input, std::unique_ptr<IndexTransform> index_transform); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually it seems like it is not used?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. 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 😆
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I missed this comment but you are correct! |
||||||
|
|
||||||
| void notify(CPPropagatorsState& p_state, ssize_t i) const; | ||||||
|
|
||||||
| Propagator* get_propagator() const; | ||||||
|
|
||||||
| ssize_t input_index() const { return p_input_; } | ||||||
|
|
||||||
| private: | ||||||
| // The propagator the advisor is watching | ||||||
| Propagator* p_; | ||||||
|
|
||||||
| // Which input | ||||||
| const ssize_t p_input_; | ||||||
|
|
||||||
| // Maps of the observed variable indices to the observed propagator indices | ||||||
| std::unique_ptr<IndexTransform> index_transform_; | ||||||
| }; | ||||||
|
|
||||||
| } // namespace dwave::optimization::cp | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| // Copyright 2026 D-Wave Systems Inc. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include "dwave-optimization/array.hpp" | ||
| #include "dwave-optimization/cp/core/cpvar.hpp" | ||
| #include "dwave-optimization/cp/core/engine.hpp" | ||
| #include "dwave-optimization/cp/core/interval_array.hpp" | ||
| #include "dwave-optimization/cp/core/propagator.hpp" | ||
| #include "dwave-optimization/cp/state/cpvar_state.hpp" | ||
| #include "dwave-optimization/graph.hpp" | ||
|
|
||
| namespace dwave::optimization::cp {} // namespace dwave::optimization::cp |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| // Copyright 2026 D-Wave Systems Inc. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <memory> | ||
|
|
||
| #include "dwave-optimization/array.hpp" | ||
| #include "dwave-optimization/cp/core/advisor.hpp" | ||
| #include "dwave-optimization/cp/core/engine.hpp" | ||
| #include "dwave-optimization/cp/core/propagator.hpp" | ||
| #include "dwave-optimization/cp/core/status.hpp" | ||
| #include "dwave-optimization/cp/state/cpvar_state.hpp" | ||
| #include "dwave-optimization/graph.hpp" | ||
|
|
||
| namespace dwave::optimization::cp { | ||
|
|
||
| // forward declaration | ||
| class CPVar; | ||
|
|
||
| class CPModel { | ||
| public: | ||
| template <class VarType, class... Args> | ||
| VarType* emplace_variable(Args&&... args) { | ||
| static_assert(std::is_base_of_v<CPVar, VarType>); | ||
|
|
||
| if (locked_) { | ||
| throw std::logic_error("cannot add a variable to a locked CP model"); | ||
| } | ||
|
|
||
| // Construct via make_unique so we can allow the constructor to throw | ||
| auto uptr = std::make_unique<VarType>(std::forward<Args&&>(args)...); | ||
| VarType* ptr = uptr.get(); | ||
|
|
||
| // Pass ownership of the lifespan to nodes_ | ||
| variables_.emplace_back(std::move(uptr)); | ||
| return ptr; // return the observing pointer | ||
| } | ||
|
|
||
| template <class PropagatorType, class... Args> | ||
| PropagatorType* emplace_propagator(Args&&... args) { | ||
| static_assert(std::is_base_of_v<Propagator, PropagatorType>); | ||
|
|
||
| if (locked_) { | ||
| throw std::logic_error("cannot add a variable to a locked CP model"); | ||
| } | ||
|
|
||
| // Construct via make_unique so we can allow the constructor to throw | ||
| auto uptr = std::make_unique<PropagatorType>(std::forward<Args&&>(args)...); | ||
| PropagatorType* ptr = uptr.get(); | ||
|
|
||
| // Pass ownership of the lifespan to nodes_ | ||
| propagators_.emplace_back(std::move(uptr)); | ||
| return ptr; // return the observing pointer | ||
| } | ||
|
|
||
| template <class SM> | ||
| CPState initialize_state() const { | ||
| static_assert(std::is_base_of_v<StateManager, SM>); | ||
| std::unique_ptr<SM> sm = std::make_unique<SM>(); | ||
| CPState state = CPState(std::move(sm), num_variables(), num_propagators()); | ||
| return state; | ||
| } | ||
|
|
||
| ssize_t num_variables() const { return variables_.size(); } | ||
|
|
||
| ssize_t num_propagators() const { return propagators_.size(); } | ||
|
|
||
| protected: | ||
| bool locked_ = false; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good catch again, haven't worked on that yet.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, that's right! |
||
| std::vector<std::unique_ptr<Propagator>> propagators_; | ||
| std::vector<std::unique_ptr<CPVar>> variables_; | ||
| }; | ||
|
|
||
| /// Interface for CP variables, allows query and modify their domain. It assumes a flattened array | ||
| /// of variables. | ||
| class CPVar { | ||
| public: | ||
| virtual ~CPVar() = default; | ||
|
|
||
| // constructor | ||
| CPVar(const CPModel& model, const dwave::optimization::ArrayNode* node_ptr, int index); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good point! |
||
|
|
||
| CPVar(const CPVar& other) = delete; | ||
| CPVar() = delete; | ||
|
|
||
| const CPModel& get_model() const { return model_; } | ||
|
|
||
| template <std::derived_from<CPVarData> StateData> | ||
| StateData* data_ptr(CPVarsState& state) const { | ||
| assert(cp_var_index_ >= 0); | ||
| return static_cast<StateData*>(state[cp_var_index_].get()); | ||
| } | ||
|
|
||
| template <std::derived_from<CPVarData> StateData> | ||
| const StateData* data_ptr(const CPVarsState& state) const { | ||
| assert(cp_var_index_ >= 0); | ||
| return static_cast<const StateData*>(state[cp_var_index_].get()); | ||
| } | ||
|
|
||
| // query the domains | ||
| double min(const CPVarsState& state, int index) const; | ||
| double max(const CPVarsState& state, int index) const; | ||
| double size(const CPVarsState& state, int index) const; | ||
| bool is_bound(const CPVarsState& state, int index) const; | ||
| bool contains(const CPVarsState& state, double value, int index) const; | ||
| bool is_active(const CPVarsState& state, int index) const; | ||
| bool maybe_active(const CPVarsState& state, int index) const; | ||
| ssize_t max_size(const CPVarsState& state) const; | ||
| ssize_t min_size(const CPVarsState& state) const; | ||
|
|
||
| ssize_t max_size() const; | ||
| ssize_t min_size() const; | ||
|
|
||
| // actions on the domains | ||
| CPStatus remove(CPVarsState& state, double value, int index) const; | ||
| CPStatus remove_above(CPVarsState& state, double value, int index) const; | ||
| CPStatus remove_below(CPVarsState& state, double value, int index) const; | ||
| CPStatus assign(CPVarsState& state, double value, int index) const; | ||
| CPStatus set_min_size(CPVarsState& state, int index) const; | ||
| CPStatus set_max_size(CPVarsState& state, int index) const; | ||
|
|
||
| // actions for the propagation engine | ||
| void schedule_all(CPState& state, const std::vector<Advisor>& advisors, ssize_t index) const; | ||
|
|
||
| // Note: maybe we need the state here.. especially if we want to attach propagators dynamically | ||
| // during the search... | ||
| // Another note: it is als possible to use a vector or struct where the struct holds the | ||
| // propagator and the event type (as an enum) that triggers it. Still considering what to do | ||
| // here.. | ||
| void propagate_on_domain_change(Advisor&& advisor); | ||
| void propagate_on_bounds_change(Advisor&& advisor); | ||
| void propagate_on_assignment(Advisor&& advisor); | ||
|
|
||
| void initialize_state(CPState& state) const; | ||
|
|
||
| /// Note: these could be state stacks that can be updated through the search. Keeping them as | ||
| /// simple vectors (static in terms of the search for now) | ||
| // They represent the propagators to trigger when the variable gets-assigned/changes | ||
| // domain/bounds change | ||
| std::vector<Advisor> on_bind; | ||
| std::vector<Advisor> on_domain; | ||
| std::vector<Advisor> on_bounds; | ||
| std::vector<Advisor> on_array_size_change; | ||
|
|
||
| protected: | ||
| const CPModel& model_; | ||
|
|
||
| // but should I have this? | ||
| const dwave::optimization::ArrayNode* node_; | ||
| const ssize_t cp_var_index_; | ||
|
|
||
| class Listener : public DomainListener { | ||
| public: | ||
| Listener(const CPVar* var, CPState& state) : var_(var), state_(state) {} | ||
|
|
||
| // domain listener overrides | ||
|
|
||
| void bind(ssize_t i) override { var_->schedule_all(state_, var_->on_bind, i); } | ||
|
|
||
| void change(ssize_t i) override { var_->schedule_all(state_, var_->on_domain, i); } | ||
|
|
||
| void change_min(ssize_t i) override { var_->schedule_all(state_, var_->on_bounds, i); } | ||
|
|
||
| void change_max(ssize_t i) override { var_->schedule_all(state_, var_->on_bounds, i); } | ||
|
|
||
| void change_array_size(ssize_t i) override { | ||
| var_->schedule_all(state_, var_->on_array_size_change, i); | ||
| } | ||
|
|
||
| private: | ||
| const CPVar* var_; | ||
| CPState& state_; | ||
| }; | ||
| }; | ||
| } // namespace dwave::optimization::cp | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| // Copyright 2026 D-Wave Systems Inc. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| #pragma once | ||
| #include <cstddef> | ||
|
|
||
| #include "dwave-optimization/common.hpp" | ||
| #include "dwave-optimization/cp/core/domain_listener.hpp" | ||
| #include "dwave-optimization/cp/core/status.hpp" | ||
|
|
||
| namespace dwave::optimization::cp { | ||
|
|
||
| /// Interface for the domains of a contiguous array of variables. It is assumed that the variables | ||
| /// are flattened and they can be optional (maybe active). This is to mimic the dynamic arrays in | ||
| /// dwave-optimization. | ||
| class DomainArray { | ||
| public: | ||
| virtual ~DomainArray() = default; | ||
|
|
||
| /// Return the total number of domains | ||
| virtual size_t num_domains() const = 0; | ||
|
|
||
| /// Return the minimum value of the index-th variable | ||
| virtual double min(int index) const = 0; | ||
|
|
||
| /// Return the maximum value of the index-th variable | ||
| virtual double max(int index) const = 0; | ||
|
|
||
| /// Return the size of the domain of the index-th variable | ||
| virtual double size(int index) const = 0; | ||
|
Comment on lines
+40
to
+41
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
|
||
| /// Return the minimum size of the array | ||
| virtual ssize_t min_size() const = 0; | ||
|
|
||
| /// Return the minimum size of the array | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maximum |
||
| virtual ssize_t max_size() const = 0; | ||
|
|
||
| /// Check whether the index-th variable is fixed or not | ||
| virtual bool is_bound(int index) const = 0; | ||
|
|
||
| /// Check whether the index-th variable contains the value | ||
| virtual bool contains(double value, int index) const = 0; | ||
|
|
||
| /// Check whether the index is active | ||
| virtual bool is_active(int index) const = 0; | ||
|
|
||
| /// Check whether the index-th variable is at least optional | ||
| virtual bool maybe_active(int index) const = 0; | ||
|
|
||
| /// Remove a value from the domain of the index-th variable | ||
| virtual CPStatus remove(double value, int index, DomainListener* l) = 0; | ||
|
|
||
| /// Remove the domain of the index-th variable above the given value | ||
| virtual CPStatus remove_above(double value, int index, DomainListener* l) = 0; | ||
|
|
||
| /// Remove the domain of the index-th variable below the given value | ||
| virtual CPStatus remove_below(double value, int index, DomainListener* l) = 0; | ||
|
|
||
| /// Remove all the domain of the index-th variable except the given value | ||
| virtual CPStatus remove_all_but(double value, int index, DomainListener* l) = 0; | ||
|
|
||
| /// Update the minimum size of the array to new_min_size | ||
| virtual CPStatus update_min_size(int new_min_size, DomainListener* l) = 0; | ||
|
|
||
| /// Update the maximum size of the array to new_max_size | ||
| virtual CPStatus update_max_size(int new_max_size, DomainListener* l) = 0; | ||
|
|
||
| protected: | ||
| // TODO: needed? | ||
| static constexpr double PRECISION = 1e-6; | ||
| }; | ||
|
|
||
| } // namespace dwave::optimization::cp | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // Copyright 2026 D-Wave Systems Inc. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| #pragma once | ||
|
|
||
| namespace dwave::optimization::cp { | ||
| class DomainListener { | ||
| public: | ||
| virtual ~DomainListener() = default; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 The plan is to have other types of listeners (other than
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The point is that if there are other variables types (other than Anyways, yes I need to follow the rule of three! I'll add that! |
||
|
|
||
| // TODO: check whether this should be removed or not | ||
| // virtual void empty() = 0; | ||
| virtual void bind(ssize_t i) = 0; | ||
| virtual void change(ssize_t i) = 0; | ||
| virtual void change_max(ssize_t i) = 0; | ||
| virtual void change_min(ssize_t i) = 0; | ||
| virtual void change_array_size(ssize_t i) = 0; | ||
| }; | ||
| } // namespace dwave::optimization::cp | ||
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.
Minor but I would prefer
constraint_propagationor similar as a "top-level" name instead ofcp. Abbreviating it on less public interfaces or within methods is fine.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.
to be precise you mean
?
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.
Yes. I could be convinced otherwise though!