Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion dwave/optimization/include/dwave-optimization/graph.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ class Graph {
void propagate(State& state, std::span<const Node*> changed) const;
void propagate(State& state, std::vector<const Node*>&& changed) const;

// TODO: better name?
std::vector<const Node*> propagate_sparse(State& state) const;

/// Given the source (changing) nodes, update the model incrementally and accept the changes
/// according to the accept function.
void propose(
Expand Down Expand Up @@ -337,6 +340,9 @@ class Node {
/// updated and may even want to eagerly incorporate changes.
virtual void update(State& state, int index) const {}

// TODO: not necessarily the same as having no diff!
virtual bool updated(const State& state) const = 0;

/// Nodes are printable
friend std::ostream& operator<<(std::ostream& os, const Node& node);

Expand Down Expand Up @@ -452,7 +458,10 @@ NodeType* Graph::emplace_node(Args&&... args) {
return ptr; // return the observing pointer
}

class ArrayNode : public Array, public virtual Node {};
class ArrayNode : public Array, public virtual Node {
public:
bool updated(const State& state) const override { return not diff(state).empty(); }
};
class DecisionNode : public Decision, public virtual Node {
public:
/// Decision nodes by definition do not have a deterministic state.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ class DisjointBitSetsNode : public DecisionNode {
ssize_t element_i
) const;

bool updated(const State& state) const override;

protected:
const ssize_t primary_set_size_;
const ssize_t num_disjoint_sets_;
Expand Down Expand Up @@ -215,6 +217,8 @@ class DisjointListsNode : public DecisionNode {
ssize_t element_j
) const;

bool updated(const State& state) const override;

protected:
const ssize_t primary_set_size_;
const ssize_t num_disjoint_lists_;
Expand Down
2 changes: 2 additions & 0 deletions dwave/optimization/include/dwave-optimization/nodes/lp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ class LinearProgramNode : public EqualityMixin<LinearProgramNodeBase, LinearProg
/// @copydoc LinearProgramNodeBase::solution()
std::span<const double> solution(const State& state) const override;

bool updated(const State& state) const override;

/// @copydoc LinearProgramNodeBase::variables_minmax()
std::pair<double, double> variables_minmax() const override;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class ArrayValidationNode : public EqualityMixin<Node> {
void propagate(State& state) const override;
void revert(State& state) const override;

bool updated(const State& state) const override { return false; }

protected:
void replace_predecessor_(ssize_t index, Node* node_ptr) override {
assert(false and "ArrayValidationNode cannot have its predecessor replaced");
Expand Down
52 changes: 52 additions & 0 deletions dwave/optimization/src/graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <algorithm>
#include <chrono>
#include <deque>
#include <queue>
#include <ranges>
#include <sstream>
#include <stdexcept>
Expand Down Expand Up @@ -175,6 +176,57 @@ void Graph::propagate(State& state, std::vector<const Node*>&& changed) const {
return propagate(state, std::span(changed));
}

std::vector<const Node*> Graph::propagate_sparse(State& state) const {
// Create a queue of nodes that are (direct) successors of updated nodes.
auto comp = [](const Node* lhs, const Node* rhs) -> bool {
// it's a max queue so we want lhs > rhs
return lhs->topological_index() > rhs->topological_index();
};
std::priority_queue<const Node*, std::vector<const Node*>, decltype(comp)> queue(comp);

// Let's start by adding all of the decisions to the queue. We go ahead an skip
// over the ones without any pending changes, though it would work just as well
// to add all of them.
// Once we track which decisions have been mutated we can be more efficient here.
for (Node* ptr : decisions_) {
if (not ptr->updated(state)) continue;
queue.emplace(ptr);
}

// We'll want to track which nodes will eventually need a commit or revert
std::vector<const Node*> updated;

while (queue.size()) {
// pull the lowest topological ordered node off the queue
const Node* ptr = queue.top();
queue.pop();

ssize_t tidx = ptr->topological_index();
state[tidx]->mark = false;

// incorporate any changes from its predecessor(s)
ptr->propagate(state);

// If calling propagate did nothing, then we're done
if (not ptr->updated(state)) continue;

// Otherwise we need to eventually commit/revert this
updated.emplace_back(ptr);

// Add all of its successors to the queue.
// In C++23 push_range() would be very helpful (and more performant)
// here.
for (const Node* successor_ptr : ptr->successors()) {
bool& seen = state[successor_ptr->topological_index()]->mark;
if (seen) continue; // already seen
seen = true;
queue.emplace(successor_ptr);
}
}

return updated;
}

// Note: we pass the vector of changed nodes by value as we expect it to be rather small. Revisit if
// it becomes too expensive.
void Graph::propose(
Expand Down
16 changes: 16 additions & 0 deletions dwave/optimization/src/nodes/collections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,14 @@ void DisjointBitSetsNode::swap_between_sets(
);
}

bool DisjointBitSetsNode::updated(const State& state) const {
const auto* data_ptr = data_ptr_<DisjointBitSetsNodeData_>(state);
for (const auto& diff : data_ptr->diffs) {
if (diff.size()) return true;
}
return false;
}

ssize_t DisjointBitSetsNode::get_containing_set_index(State& state, ssize_t element) const {
return data_ptr_<DisjointBitSetsNodeData_>(state)->get_containing_set_index(element);
}
Expand Down Expand Up @@ -824,6 +832,14 @@ void DisjointListsNode::pop_to_list(
);
}

bool DisjointListsNode::updated(const State& state) const {
const auto* data_ptr = data_ptr_<DisjointListStateData_>(state);
for (const auto& diff : data_ptr->all_list_updates) {
if (diff.size()) return true;
}
return false;
}

DisjointListNode::DisjointListNode(DisjointListsNode* disjoint_list_node) :
ArrayOutputMixin(Array::DYNAMIC_SIZE),
disjoint_list_node_ptr_(disjoint_list_node),
Expand Down
4 changes: 4 additions & 0 deletions dwave/optimization/src/nodes/lp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,10 @@ std::span<const double> LinearProgramNode::solution(const State& state) const {
return data_ptr_<LinearProgramNodeData>(state)->result.solution();
}

bool LinearProgramNode::updated(const State& state) const {
return true; // TODO: think about this more
}

std::pair<double, double> LinearProgramNode::variables_minmax() const { return variables_minmax_; }

std::span<const ssize_t> LinearProgramNode::variables_shape() const { return c_ptr_->shape(); }
Expand Down
18 changes: 18 additions & 0 deletions tests/cpp/test_graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,24 @@ TEST_CASE("Graph::commit(), Graph::descendants(), Graph::propagate(), and Graph:
auto descendants = graph.descendants({x_ptr});
CHECK_THAT(descendants, RangeEquals(std::vector<Node*>{x_ptr, z_ptr}));
}

SECTION("propagate_sparse()") {
SECTION("no changes") {
auto updated = graph.propagate_sparse(state);
CHECK(updated.empty());
}

SECTION("one decision") {
x_ptr->flip(state, 0);

auto updated = graph.propagate_sparse(state);

CHECK_THAT(updated, RangeEquals(std::vector<Node*>{x_ptr, z_ptr}));
}

}


SECTION("Propagate all") {
CHECK(x_ptr->view(state).front() == 0);
CHECK(y_ptr->view(state).front() == 0);
Expand Down