Implement state checkpointing for decision nodes - #587
Conversation
6df7006 to
953304e
Compare
|
|
||
| CollectionCheckpoint_::CollectionCheckpoint_(CollectionStateData_* state_ptr) : | ||
| updates_(), | ||
| drop_(state_ptr->all_updates_.size()), // so we ignore any updates added before we're made |
There was a problem hiding this comment.
What happens if some mutations are made to the node, then you make a checkpoint (causing the drop_ to be set > 0), and then you revert, make more mutations, and commit? Won't this cause some updates to be "dropped" that are actually relevant to the checkpoint?
There was a problem hiding this comment.
Actually, I think I mean "make another checkpoint" as final step in the example rather than commit.
b4679c2 to
214c5e2
Compare
This avoids using GCC11 which had some bugs in their ranges implementation.
86b97e8 to
9cd035b
Compare
f1aa563 to
06b9dc5
Compare
06b9dc5 to
f70699e
Compare
30637e6 to
5fbe15b
Compare
|
|
||
| // Overloads required by the DecisionNode ABC ***************************** | ||
|
|
||
| // DynamicArrayTestingNode does not impement checkpointing |
5fbe15b to
4402568
Compare
| } | ||
| } | ||
|
|
||
| void Graph::propose(State& state) const { |
|
|
||
| set_ptr->assign_from_checkpoint(state, std::move(checkpoint0)); | ||
|
|
||
| // CHECK_THAT(set_ptr->view(state), RangeEquals({0, 1})); |
| } | ||
| } | ||
|
|
||
| // TODO: within one propagation |
There was a problem hiding this comment.
this is done, need to remove this comment
| auto checkpoint = ptr->checkpoint(state); // 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 | ||
|
|
||
| THEN("We can mutate and then assign from that checkpoint") { | ||
| ptr->set_value(state, 0, 1); // 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 |
There was a problem hiding this comment.
// 1, 1, 0, 1, 0, 1, 0, 1, 0, 1since you called set_value()
| ptr->exchange(state, 0, 2); // [-2, -4, -4, -2, 0, 0, 2, 2, 4, 4] | ||
| ptr->set_value(state, 3, 1); // [-2, -4, -4, 1, 0, 0, 2, 2, 4, 4] | ||
|
|
||
| THEN("We can commit, then assign from the checkpoint") { |
There was a problem hiding this comment.
Nit:
THEN("We can propose, then assign from the checkpoint") {I realize propose is propagate and commit. As this is a Nit, I am not going to comment at all duplicate instances.
| AND_WHEN("We create a checkpoint to that state") { | ||
| auto checkpoint = ptr->checkpoint(state); // 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 | ||
|
|
||
| THEN("We can mutate and then assign from that checkpoint") { |
There was a problem hiding this comment.
Nit
THEN("We can mutate, then propose, and then assign from that checkpoint") {| CHECK_THAT(inode_ptr->view(state), RangeEquals({8, 8, 8, 8, 8, 8, -3, 3})); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
A bit of a pain, but I think it might be worth adding a single test for BinaryNode with checkpointing to check that all the correct indices are cached with 1s and 0s (I am referring to the BinaryNode DisjointSparseSet).
THEN("Sum constraint sums, tracked indices, and state are correct") {
CHECK(bnode_ptr->sum_constraints_lhs(state).size() == 1);
CHECK(bnode_ptr->sum_constraints_lhs(state).data()[0].size() == 1);
CHECK_THAT(bnode_ptr->sum_constraints_lhs(state)[0], RangeEquals({3}));
check_indices<true>(state, bnode_ptr, 0, 0, {1, 2, 5}); // <- ***** This check is the important one ******
check_indices<false>(state, bnode_ptr, 0, 0, {0, 3, 4}); // <- ***** And this one ******
CHECK_THAT(bnode_ptr->view(state), RangeEquals(expected_init));
}| const auto& [idx, old, _] : | ||
| state_data->diff() | std::views::reverse | std::views::take(excess_updates) | ||
| ) { | ||
| state_data->set(idx, old); |
There was a problem hiding this comment.
I would just add a note that this by-passes (currently) the sum constraint lhs tracking.
There was a problem hiding this comment.
Looking at this again, can you remind we why this is fine?
| assert(size_ >= 0 && static_cast<std::size_t>(size_) == buffer.size()); | ||
| } | ||
|
|
||
| // Commit the changes and clear the diff by returning the diff buffer. |
There was a problem hiding this comment.
Confused where the commit() is and am feeling like I am missing something.
There was a problem hiding this comment.
Commiting the changes just means keeping the status quo 😄
There was a problem hiding this comment.
Wait. Given a committed state, if we then make a series of changes (and create a diff) this method detaches the diff. Is this considered a silent commit()? The state is still modified but now we don't have the diff to now how it was modified?
| size_ = buffer.size(); | ||
| } | ||
|
|
||
| // Commit the changes and clear the diff by returning the diff buffer. |
There was a problem hiding this comment.
Same doc string comments as commit_and_detach(). Intentional?
| if (auto* prev_ptr = static_cast<DiffCheckpoint*>(prev_ptr_)) { | ||
| prev_ptr->commit_updates(std::vector<Update>(diff.begin(), diff.end())); | ||
| assert(prev_ptr->drop_ == 0); | ||
| } |
There was a problem hiding this comment.
To be clear, we are doing the static_cast because the prev_ptr_ member is a LinkedListCheckpoint which does not have the method commit_updates(). Functionally, the only reason the static_cast would return a nullptr is if prev_ptr_ is a nullptr already.
I am just comparing this to the destructor which checks if (prev_ptr_ == nullptr) and does not have a if around static_cast<DiffCheckpoint*>(prev_ptr_). It feels like this should either be
if (auto* prev_ptr = static_cast<DiffCheckpoint*>(prev_ptr_)) {
prev_ptr->commit_updates(std::vector<Update>(diff.begin(), diff.end()));
assert(prev_ptr->drop_ == 0);
return;
}
assert(prev_ptr_ == nullptr);or should resemble the destructor
if (prev_ptr_ == nullptr) return;
auto* prev_ptr = static_cast<DiffCheckpoint*>(prev_ptr_);
assert(prev_ptr); // <- I added this
prev_ptr->commit_updates(std::vector<Update>(diff.begin(), diff.end()));
assert(prev_ptr->drop_ == 0);| if (prev_ptr_ == nullptr) return; | ||
|
|
||
| // otherwise we need to transfer our info over | ||
| auto* prev_ptr = static_cast<DiffCheckpoint*>(prev_ptr_); |
There was a problem hiding this comment.
See comment above. If we keep this structure, I would consider adding
assert(prev_ptr);| // otherwise we need to transfer our info over | ||
| auto* prev_ptr = static_cast<DiffCheckpoint*>(prev_ptr_); | ||
| assert(prev_ptr->drop_ == 0); | ||
| for (auto& updates : updates_) prev_ptr->commit_updates(std::move(updates)); |
There was a problem hiding this comment.
The reason we do not do
prev_ptr->commit_updates(this->detach_updates());is because drop_ (which I would love a doc string for 😄) says how many of the vectors in vector<vector<Updates>> we drop?
There was a problem hiding this comment.
how many updates of the last vector. And agree drop_ needs a docstring, @alexzucca90 had the same feedback
There was a problem hiding this comment.
The last vector! Okay. In that case, why do the loop as opposed to
prev_ptr->commit_updates(this->detach_updates());There was a problem hiding this comment.
prev_ptr->commit_updates() takes a single diff (usually from the node state). Here we want to transfer a sequence of diffs. We could flatten, or add an overload I suppose..
There was a problem hiding this comment.
It does not make a difference really to me. Just trying to understand. I had thought that
auto detach_updates() {
auto updates = std::move(updates_) | std::views::join;
assert(updates_.empty());
return updates;
}would result in a std::vector<Update> since updates_ is a std::vector<std::vector<Update>>. I had not thought you would need an overload?
|
I think I need to take another documentation pass. I'll try to do that today |
fastbodin
left a comment
There was a problem hiding this comment.
Officially gone through everything. I need to look at the number.cpp and collections.cpp again. On the whole, looks good. Most of my comments above are clarifications. I will resolved them ASAP to avoid clogging the thread on this PR.
| // Right now, you can only revert to the most recent checkpoint. It's | ||
| // pretty straightforward to support going further back, but this is all | ||
| // we need right now. | ||
| assert(this->checkpoint_ptr<CollectionCheckpoint_>() == checkpoint_ptr); |
There was a problem hiding this comment.
Took me a hot minute to realize this was templated from
class CheckpointableState {
protected:
template <std::derived_from<LinkedListCheckpoint> T>
T* checkpoint_ptr() {
return static_cast<T*>(prev_ptr_);
}| // DisjointBitSetsNode is on the way out, so let's do the simplest possible | ||
| // implementation for now. |
| State& state, | ||
| std::unique_ptr<NodeStateCheckpoint>& checkpoint | ||
| ) const { | ||
| const DisjointBitSetsCheckpoint_* checkpoint_ptr = |
| check.reset(); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
I love this test and wish there was a final CHECK() we could do :rofl
|
|
||
| auto* checkpoint_ptr = static_cast<NumberNodeCheckpoint_*>(checkpoint.get()); | ||
|
|
||
| // todo: assert that this checkpoint is the latest |
There was a problem hiding this comment.
Testing my understanding. You mean
// Right now, you can only revert to the most recent checkpoint. It's
// pretty straightforward to support going further back, but this is all
// we need right now.
assert(this->checkpoint_ptr<NumberNodeCheckpoint_>() == checkpoint_ptr);| const auto& [idx, old, _] : | ||
| state_data->diff() | std::views::reverse | std::views::take(excess_updates) | ||
| ) { | ||
| state_data->set(idx, old); |
There was a problem hiding this comment.
Looking at this again, can you remind we why this is fine?
Closes #510
Closes #552 by replacing it
AI Generation Disclosure
No AI was used to write the code. I intend to use it to review this PR.