-
Notifications
You must be signed in to change notification settings - Fork 35
Add method for removing redundant nodes #571
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?
Changes from all commits
82cc356
57638a8
16a2938
c0e52bc
948c199
0443585
2985dcf
1cece38
055fe62
770a9cb
5b213bc
47e1be7
cd107a0
6915ba4
edf9a68
f7a75d1
cf7f812
b1df040
f643bca
08728d9
7e3ccae
918d197
b43d9d3
c60bcb7
d0dd3d3
a7912dd
3583a42
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 |
|---|---|---|
|
|
@@ -928,6 +928,46 @@ cdef class _Graph: | |
| """ | ||
| return self.num_nodes() | ||
|
|
||
| def remove_redundant_symbols(self, time_limit_s=None): | ||
| """Remove redundant symbols from the model. | ||
|
|
||
| Symbols are redundant if they are the same type, they share the | ||
| same predecessors, and they encode the same operation. | ||
|
|
||
| Args: | ||
| time_limit_s (float): The maximum amount of time to spend trying | ||
| to remove redundant symbols. If not provided, the method will | ||
| run until there are no redundant symbols to remove. | ||
|
|
||
| Returns: | ||
| int: Number of symbols removed. | ||
|
|
||
| Examples: | ||
|
|
||
| >>> from dwave.optimization import Model | ||
| ... | ||
| >>> model = Model() | ||
| ... | ||
| >>> x = model.integer(5) | ||
| ... | ||
|
Collaborator
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. Nit: I find that the |
||
| >>> -(x + 1).sum() # doctest: +ELLIPSIS | ||
| <dwave.optimization.symbols.unaryop.Negative at ...> | ||
| >>> -(x + 1).sum() # doctest: +ELLIPSIS | ||
| <dwave.optimization.symbols.unaryop.Negative at ...> | ||
| >>> model.num_symbols() | ||
| 8 | ||
| >>> model.remove_redundant_symbols() | ||
| 3 | ||
| >>> model.num_symbols() | ||
| 5 | ||
| """ | ||
| if self.is_locked(): | ||
| raise ValueError("cannot remove symbols from a locked model") | ||
| if time_limit_s is None: | ||
| return self._graph.remove_redundant_nodes(False) | ||
| else: | ||
| return self._graph.remove_redundant_nodes(False, time_limit_s) | ||
|
|
||
| def remove_unused_symbols(self): | ||
| """Remove unused symbols from the model. | ||
|
|
||
|
|
@@ -981,7 +1021,7 @@ cdef class _Graph: | |
| """ | ||
| if self.is_locked(): | ||
| raise ValueError("cannot remove symbols from a locked model") | ||
| return self._graph.remove_unused_nodes() | ||
| return self._graph.remove_unused_nodes(False) | ||
|
|
||
| def state_size(self): | ||
| r"""Return an estimate of the size, in bytes, of a model's state. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -156,6 +156,17 @@ class Graph { | |
| /// Reset the state of the given node and all successors recursively. | ||
| static void recursive_reset(State& state, const Node* ptr); | ||
|
|
||
| /// Remove redundant nodes from the graph. | ||
| /// | ||
| /// Redundant nodes are ones that are Node::equal_to() another node in the | ||
| /// graph. | ||
| /// | ||
| /// Returns the number of nodes removed from the graph. | ||
| ssize_t remove_redundant_nodes( | ||
| bool ignore_listeners = false, | ||
| double time_limit_s = std::numeric_limits<double>::infinity() | ||
| ); | ||
|
|
||
| /// Remove unused nodes from the graph. | ||
| /// | ||
| /// This method will reset the topological sort if there is one. | ||
|
|
@@ -263,6 +274,12 @@ class Node { | |
| /// derived from its predecessors. Defaults to `true`, except for decisions. | ||
| virtual bool deterministic_state() const { return true; } | ||
|
|
||
| /// Test whether two nodes are equal. Each node class defines equality for | ||
| /// itself but nodes *must* share the same set of | ||
| /// predecessors (permutations are sometimes allowed) and they *must* be the | ||
| /// same type. Also that they can have false negatives in some cases. | ||
| virtual bool equal_to(const Node& rhs) const = 0; | ||
|
|
||
| /// Return a shared pointer to a bool value. When the node is destructed | ||
| /// the bool will be set to True | ||
| std::shared_ptr<const bool> expired_ptr() const { return expired_ptr_; } | ||
|
|
@@ -305,6 +322,11 @@ class Node { | |
| /// Return the successors of the node. | ||
| const std::vector<SuccessorView>& successors() const { return successors_; } | ||
|
|
||
| /// Take the successors from `from` and make them successors of the node. | ||
| /// Note that this bypasses most checks! Use with extreme caution as it | ||
| /// make create undefined models. | ||
| void take_successors(Node& from); | ||
|
|
||
| /// The current topological index. Will be negative if unsorted. | ||
| ssize_t topological_index() const { return topological_index_; } | ||
|
|
||
|
|
@@ -322,6 +344,7 @@ class Node { | |
| friend void Graph::reset_topological_sort(); | ||
| template <class NodeType, class... Args> | ||
| friend NodeType* Graph::emplace_node(Args&&...); | ||
| friend ssize_t Graph::remove_redundant_nodes(bool, double); | ||
| friend ssize_t Graph::remove_unused_nodes(bool); | ||
|
|
||
| protected: | ||
|
|
@@ -380,6 +403,10 @@ class Node { | |
| // Remove a successor. *Does not* remove itself from it's successor's predecessors. | ||
| ssize_t remove_successor_(const Node* ptr); | ||
|
|
||
| // Replace the predecessor at `index` with the node specified by `node_ptr`. | ||
| // Does not update `node_ptr`. | ||
| virtual void replace_predecessor_(ssize_t index, Node* node_ptr); | ||
|
|
||
| private: | ||
| ssize_t topological_index_ = -1; // negative is unset | ||
|
|
||
|
|
@@ -431,6 +458,10 @@ class DecisionNode : public Decision, public virtual Node { | |
| /// Decision nodes by definition do not have a deterministic state. | ||
| bool deterministic_state() const final { return false; } | ||
|
|
||
| /// Decision can only ever be equal to themselves because they are | ||
| /// independent variables. | ||
| bool equal_to(const Node& rhs) const final { return static_cast<const Node*>(this) == &rhs; } | ||
|
|
||
| /// Decisions don't have predecessors so no one should be calling update(). | ||
| /// Always throws a logic_error. | ||
| [[noreturn]] void update(State& state, int index) const override; | ||
|
|
@@ -440,4 +471,44 @@ class DecisionNode : public Decision, public virtual Node { | |
| bool removable_() const override { return false; } | ||
| }; | ||
|
|
||
| /// Provide an implementation of equal_to() that defers to a type-specific | ||
| /// implementation. | ||
| template<std::derived_from<Node> Base, typename Derived = void> | ||
| struct EqualityMixin : Base { | ||
| /// @copydoc Node::equal_to() | ||
| bool equal_to(const Node& rhs) const final { | ||
| // Every node is equal with itself | ||
| if (&rhs == static_cast<const Node*>(this)) return true; | ||
|
|
||
| // A node cannot be equal if they are not of the same type. | ||
| const auto* rhs_ptr = dynamic_cast<const Derived*>(&rhs); | ||
| if (rhs_ptr == nullptr) return false; | ||
|
|
||
| // lhs and rhs are the same type, so let's go to our type-specific | ||
| // method. | ||
| return equal_to(*rhs_ptr); | ||
| } | ||
|
|
||
| // Nodes must implement `equal_to()` for their own type. | ||
| virtual bool equal_to(const Derived& rhs) const = 0; | ||
| }; | ||
|
|
||
| /// Overload for EqualityMixin that does not require a class-specific equal_to() | ||
|
Member
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. A doubt I have is whether these two overloads are different enough that they should actually be given different names 🤔 |
||
| /// method overload. This simply checks that they have the same type and same | ||
| /// predecessors. | ||
| template<std::derived_from<Node> Base> | ||
| struct EqualityMixin<Base, void> : Base { | ||
| /// @copydoc Node::equal_to() | ||
| bool equal_to(const Node& rhs) const final { | ||
| // Every node is equal with itself | ||
| if (&rhs == static_cast<const Node*>(this)) return true; | ||
|
|
||
| // Nodes that are not the same type cannot be equal | ||
| if (typeid(*this) != typeid(rhs)) return false; | ||
|
|
||
| // Nodes that are of the same type must have the same predecessors | ||
| return std::ranges::equal(this->predecessors(), rhs.predecessors()); | ||
| } | ||
| }; | ||
|
|
||
| } // namespace dwave::optimization | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -138,6 +138,9 @@ class DisjointBitSetNode : public ArrayOutputMixin<ArrayNode> { | |||||
|
|
||||||
| std::span<const Update> diff(const State& state) const override; | ||||||
|
|
||||||
| // A DisjointBitSetNode can only ever be equal to itself. | ||||||
|
Collaborator
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. Nit.
Suggested change
|
||||||
| bool equal_to(const Node& rhs) const override; | ||||||
|
|
||||||
| /// @copydoc Array::integral() | ||||||
| bool integral() const override; | ||||||
|
|
||||||
|
|
@@ -228,6 +231,9 @@ class DisjointListNode : public ArrayOutputMixin<ArrayNode> { | |||||
|
|
||||||
| std::span<const Update> diff(const State& state) const override; | ||||||
|
|
||||||
| // A DisjointListNode can only ever be equal to itself. | ||||||
|
Collaborator
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. Nit.
Suggested change
|
||||||
| bool equal_to(const Node& rhs) const override; | ||||||
|
|
||||||
| /// @copydoc Array::integral() | ||||||
| bool integral() const override; | ||||||
|
|
||||||
|
|
||||||
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.
Worth adding a note in here that this is ineffective on named symbols?