Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
953304e
Implement state checkpointing for CollectionNode
arcondello Jul 17, 2026
214c5e2
Fix reverts and dangling pointers for CollectionNode checkpoints
arcondello Jul 20, 2026
9cd035b
Use offical Python images in CircleCI
arcondello Jul 20, 2026
6ec49f9
Add LinkedListCheckpoint helper class
arcondello Jul 20, 2026
fc44824
Fix the copying of checkpointed states
arcondello Jul 20, 2026
f711e63
Drop unused NodeStateCheckpoint::valid() method
arcondello Jul 20, 2026
e85f85f
Add DiffCheckpoint class for checkpoints that track diffs
arcondello Jul 21, 2026
f70699e
Go back to using CircleCI image for some CI tasks
arcondello Jul 21, 2026
ef950e4
Add NumberNode::checkpoint()
arcondello Jul 23, 2026
e61a70f
Fix assigning from a checkpoint after mutation
arcondello Jul 27, 2026
e845381
Add test for BinaryNode::assign_from_checkpoint
arcondello Jul 27, 2026
4402568
Make all DecisionNodes implement checkpointing
arcondello Jul 27, 2026
ec4abfc
Expand docstrings for checkpoint implementations
arcondello Aug 4, 2026
9ed9e4c
Add Graph::pop_decision() and Graph::swap_decisions() methods
arcondello Aug 4, 2026
bcbda09
Make license headers consistent
arcondello Aug 5, 2026
8f4b24c
Merge pull request #601 from arcondello/fix/headers
arcondello Aug 5, 2026
52eea60
Fix Graph::pop_decision()
arcondello Aug 5, 2026
e9bad27
Fix Graph::swap_decision()
arcondello Aug 5, 2026
5a80bc2
Merge pull request #600 from arcondello/feature/decision-mutation
arcondello Aug 6, 2026
f54877d
Address nonfunctional comments from code review
arcondello Aug 6, 2026
7343b41
Address functional comments from code review
arcondello Aug 6, 2026
769c957
Merge remote-tracking branch 'upstream/main' into feature/decision-ch…
arcondello Aug 6, 2026
885c8f1
Update NumberNodeStateData to hold a reference to NumberNode
arcondello Aug 7, 2026
b0f6c07
Make a single (virtual) overload for NumberNodeStateData::update()
arcondello Aug 7, 2026
aef592b
Update NumberNode::exhange() to handle subclasses and remove BinaryNo…
arcondello Aug 7, 2026
fe7046b
Add comment to BinaryNodeStateData::update() and remove dead code
arcondello Aug 7, 2026
7c05cce
Update NumberNode::set_value() to handle subclasses
arcondello Aug 7, 2026
8bf618f
Make NumberNodeStateData::update() private
arcondello Aug 7, 2026
bd275b5
Merge pull request #587 from arcondello/feature/decision-checkpoints
arcondello Aug 10, 2026
a27e9b7
Add dependency-groups for oldest dependencies
arcondello Aug 10, 2026
f587434
Merge pull request #605 from arcondello/feature/dependency-testing-re…
arcondello Aug 11, 2026
67cd2b8
Use SciPy for LinearProgram symbol calculations when available
arcondello Aug 10, 2026
bd55226
Merge pull request #604 from arcondello/feature/SciPy-LP
arcondello Aug 11, 2026
55c5bbc
Build wheels for Python 3.15
arcondello Aug 11, 2026
450b9f4
Add C++ `IsDisjointCoverNode` and python `is_disjoint_cover` function
smharwood Aug 3, 2026
ccdd549
Fix IsDisjointCoverNode comparison and replace predecessors
smharwood Aug 11, 2026
9a893d6
Merge pull request #606 from arcondello/feature/Python3.15
arcondello Aug 11, 2026
2b780ae
Add const overload for DisjointBitSetsNode::get_containing_set_index()
hurdwave Aug 11, 2026
cfe36a0
Revert deprecation of disjoint_lists_symbol
smharwood Aug 11, 2026
f28dc39
Fix some code styling and doc strings
smharwood Aug 11, 2026
dc1f170
Merge pull request #557 from smharwood/feature/disjoint-node
arcondello Aug 11, 2026
e7fb08a
Remove DisjointBitSetsNode::get_containing_set_index() accepting a no…
hurdwave Aug 11, 2026
624dd62
Merge pull request #607 from hurdwave/feature/disjoint-bitsets-const-…
arcondello Aug 11, 2026
47be192
Use PyCapsule for creating Symbols from Nodes
arcondello Aug 12, 2026
a5f7133
Merge pull request #608 from arcondello/feature/use-capsule
arcondello Aug 12, 2026
1fcb5bf
Add additional ``BinaryNode`` checkpoint tests
fastbodin Aug 12, 2026
aba0580
Merge pull request #609 from fastbodin/add_binary_index_check_for_che…
arcondello Aug 12, 2026
3987f10
Don't serialize the states of intermediate variables
arcondello Aug 4, 2026
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
67 changes: 44 additions & 23 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ commands:
parameters:
cibw-version:
type: string
default: 3.4.1 # latest as of April 2026
default: 4.2.0 # latest as of August 2026
steps:
- run:
name: run cibuildwheel
Expand All @@ -30,7 +30,7 @@ environment: &global-environment
jobs:
python-linux:
docker:
- image: cimg/python:3.13 # just need a version that can install cibuildwheel
- image: cimg/python:3.13 # need a version that can install cibuildwheel and that has docker

environment:
<<: *global-environment
Expand All @@ -57,7 +57,7 @@ jobs:

python-linux-debug:
docker:
- image: cimg/python:3.10
- image: python:3.10

steps:
- checkout
Expand Down Expand Up @@ -92,7 +92,6 @@ jobs:
name: test with installed package
command: |
. env/bin/activate
pip install --group test
cd tests/
python -Werror -m unittest

Expand Down Expand Up @@ -127,7 +126,7 @@ jobs:

python-sdist:
docker:
- image: cimg/python:3.10
- image: python:3.10

steps:
- checkout
Expand All @@ -154,9 +153,7 @@ jobs:
parameters:
python-version:
type: string
dependency-versions:
type: string
optional-dependency-versions:
wheel-tag:
type: string

docker:
Expand All @@ -167,23 +164,49 @@ jobs:
- attach_workspace:
at: dist
- run:
name: install package with dependencies
name: test with latest required dependencies
command: |
python -m venv env
. env/bin/activate
pip install pip --upgrade
pip install --upgrade --only-binary=:all: \
<< parameters.dependency-versions >>
pip install dwave-optimization --no-index -f dist/ --no-deps --force-reinstall
- run: *run-tests
pip install dist/dwave*optimization-*-<< parameters.wheel-tag >>-*.whl
cd tests/
python -Werror -m unittest
rm -r ../env
- run:
name: install optional dependencies
name: test with latest optional dependencies
command: |
python -m venv env
. env/bin/activate
pip install --upgrade --only-binary=:all: \
<< parameters.dependency-versions >> \
<< parameters.optional-dependency-versions >> \
- run: *run-tests
pip install pip --upgrade
pip install "$(echo dist/dwave*optimization-*-<< parameters.wheel-tag >>-*.whl)[all]"
cd tests/
python -Werror -m unittest
rm -r ../env
- run:
name: test with oldest required dependencies
command: |
python -m venv env
. env/bin/activate
pip install pip --upgrade
pip install \
--group dependencies-oldest \
dist/dwave*optimization-*-<< parameters.wheel-tag >>-*.whl
cd tests/
python -Werror -m unittest
rm -r ../env
- run:
name: test with oldest optional dependencies
command: |
python -m venv env
. env/bin/activate
pip install pip --upgrade
pip install \
--group dependencies-all-oldest \
dist/dwave*optimization-*-<< parameters.wheel-tag >>-*.whl
cd tests/
python -Werror -m unittest
rm -r ../env

cpp-gcc:
parameters:
Expand Down Expand Up @@ -377,15 +400,13 @@ workflows:
- python-dependencies:
name: python-dependencies-oldest
python-version: "3.10"
dependency-versions: numpy==1.21.3
optional-dependency-versions: dimod==0.12.0 scikit-learn==1.6.0
wheel-tag: cp310-cp310
requires:
- python-linux
- python-dependencies:
name: python-dependencies-latest
python-version: "3.14"
dependency-versions: numpy
optional-dependency-versions: dimod scikit-learn
python-version: "3.14" # As of August 11 2026, dimod, sklearn, scipy don't have 3.15 wheels
wheel-tag: cp312-abi3
requires:
- python-linux
- cpp-gcc:
Expand Down
2 changes: 1 addition & 1 deletion dwave/optimization/__init__.pxd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2023 D-Wave Systems Inc.
# Copyright 2023 D-Wave
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion dwave/optimization/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2023 D-Wave Systems Inc.
# Copyright 2023 D-Wave
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion dwave/optimization/_build/_version.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3

# Copyright 2024 D-Wave Inc.
# Copyright 2024 D-Wave
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion dwave/optimization/_model.pxd
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# cython: auto_pickle=False

# Copyright 2024 D-Wave Inc.
# Copyright 2024 D-Wave
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion dwave/optimization/_model.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2024 D-Wave Systems Inc.
# Copyright 2024 D-Wave
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
60 changes: 22 additions & 38 deletions dwave/optimization/_model.pyx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2024 D-Wave Inc.
# Copyright 2024 D-Wave
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -26,6 +26,7 @@ import zipfile
import numpy as np

from cpython cimport Py_buffer
from cpython.pycapsule cimport PyCapsule_GetPointer, PyCapsule_New
from cpython.ref cimport PyObject
from cython.operator cimport dereference as deref, preincrement as inc
from cython.operator cimport typeid
Expand Down Expand Up @@ -91,10 +92,14 @@ cdef object symbol_from_ptr(_Graph model, cppNode* node_ptr):
# IndexError would be returned by .at()
raise RuntimeError("given pointer cannot be cast to a known node type") from None

# In order to get nice polymorphism, it's much easier to pass the dispatch
# through Python, so we construct a generic Symbol holding the pointer and then
# construct the specific symbol from it.
return cls._from_symbol(Symbol.from_ptr(model, node_ptr))
# We'll use a PyCapsule to pass a node pointer through the Python layer so
# that we can let `cls` determine the type.

# Even though PyCapsule_New returns a PyObject*, Cython automatically makes
# it an object (with appropriate refcounting) so that's nice.
cap = PyCapsule_New(node_ptr, 'Node*', NULL)

return cls._from_ptr(model, cap)


cdef class _Graph:
Expand Down Expand Up @@ -1129,12 +1134,6 @@ cdef class Symbol:
self.node_ptr = node_ptr
self.expired_ptr = node_ptr.expired_ptr()

def _deterministic_state(self):
"""Return ``True`` if the symbol's state is uniquely determined by its
predecessors.
"""
return self.node_ptr.deterministic_state()

def equals(self, other):
"""Compare whether two symbols are identical.

Expand Down Expand Up @@ -1195,17 +1194,11 @@ cdef class Symbol:
return obj

@classmethod
def _from_symbol(cls, Symbol symbol):
# Disallow lateral casts or demotions.
# This is to prevent, say, an Add to be constructed from a Subtract
# There are ways around it, but this method is private anyway so it
# should be enough of a discouragement and for safety.
if not issubclass(cls, type(symbol)):
raise TypeError(f"cannot construct a {cls.__name__} from a {type(symbol).__name__}")

cdef Symbol obj = cls.__new__(cls)
obj.initialize_node(symbol.model, symbol.node_ptr)
return obj
def _from_ptr(cls, model, capsule):
"""Create a Symbol from a Python capsule containing a Node pointer."""
cdef Symbol sym = cls.__new__(cls)
sym.initialize_node(model, <cppNode*>(PyCapsule_GetPointer(capsule, 'Node*')))
return sym

@classmethod
def _from_zipfile(cls, zf, directory, _Graph model, predecessors):
Expand Down Expand Up @@ -1681,23 +1674,14 @@ cdef class ArraySymbol(Symbol):
self.initialize_node(model, array_ptr)

@classmethod
def _from_symbol(cls, Symbol symbol):
"""Construct an ArraySymbol from another Symbol."""
# Disallow lateral casts or demotions.
# This is to prevent, say, an Add to be constructed from a Subtract
# There are ways around it, but this method is private anyway so it
# should be enough of a discouragement and for safety.
if not issubclass(cls, type(symbol)):
raise TypeError(f"cannot construct a {cls.__name__} from a {type(symbol).__name__}")

# Now try to "promote" the type and raise an error if that fails.
cdef cppArrayNode* ptr = dynamic_cast_ptr[cppArrayNode](symbol.node_ptr)
if not ptr:
raise TypeError(f"given symbol cannot construct a {cls.__name__}")
def _from_ptr(cls, model, capsule):
cdef ArraySymbol sym = super()._from_ptr(model, capsule)

cdef ArraySymbol obj = cls.__new__(cls)
obj.initialize_arraynode(symbol.model, ptr)
return obj
sym.array_ptr = dynamic_cast_ptr[cppArrayNode](sym.node_ptr)
if not sym.array_ptr:
raise TypeError(f"given pointer cannot construct an ArrayNode")

return sym

# Opt ArraySymbol out of default interoperability with NumPy ufuncs. We then
# add explicit support with our various __<op>__() and __r<op>__ methods.
Expand Down
2 changes: 1 addition & 1 deletion dwave/optimization/generators.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2024 D-Wave Inc.
# Copyright 2024 D-Wave
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
5 changes: 4 additions & 1 deletion dwave/optimization/include/dwave-optimization/array.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023 D-Wave Systems Inc.
// Copyright 2023 D-Wave
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -347,6 +347,9 @@ struct Update {
// Return true if the update does nothing - that is old and value are the same.
bool identity() const { return null() || old == value; }

// Return the update that would undo the current update
Update inverse() const { return Update(index, value, old); }

// Use NaN to represent the "nothing" value used in placements/removals
static constexpr double nothing = std::numeric_limits<double>::signaling_NaN();

Expand Down
31 changes: 22 additions & 9 deletions dwave/optimization/include/dwave-optimization/graph.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023 D-Wave Systems Inc.
// Copyright 2023 D-Wave
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -135,6 +135,10 @@ class Graph {
ArrayNode* objective() noexcept { return objective_ptr_; }
const ArrayNode* objective() const noexcept { return objective_ptr_; }

/// Remove the last decision. Must not have any successors or the behavior
/// is undefined.
void pop_decision();

/// Call propagate on every `Node` in the `Graph`.
void propagate(State& state) const;

Expand All @@ -151,6 +155,11 @@ class Graph {
std::function<bool(const Graph&, State&)> accept = [](const Graph&, State&) { return true; }
) const;

/// Propagate any pending changes to all nodes in the graph and commit them.
void propose(State& state) const;
// dev note: the name is a bit funny in this case, but we essentially want
// an overload for a "default" `sources` and `accept`.

/// Initialize the state of the given node and all predecessors recursively.
static void recursive_initialize(State& state, const Node* ptr);
/// Reset the state of the given node and all successors recursively.
Expand Down Expand Up @@ -196,6 +205,9 @@ class Graph {
/// To unset the objective provide nullptr.
void set_objective(ArrayNode* objective_ptr);

/// Swap the topological indices of the two given decision nodes.
void swap_decisions(DecisionNode* x_ptr, DecisionNode* y_ptr);

/// Sort the nodes topologically. This "locks" the model in that nodes cannot
/// be added to a topologically sorted model without invalidating the topological
/// ordering.
Expand Down Expand Up @@ -270,10 +282,6 @@ class Node {
/// Commit any changing updates to the node.
virtual void commit(State& state) const = 0;

/// Return true if the node's state is deterministic - that is it's uniquely
/// 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
Expand Down Expand Up @@ -340,12 +348,13 @@ class Node {
/// Nodes are printable
friend std::ostream& operator<<(std::ostream& os, const Node& node);

friend void Graph::topological_sort();
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);
friend void Graph::reset_topological_sort();
friend void Graph::swap_decisions(DecisionNode* x_ptr, DecisionNode* y_ptr);
friend void Graph::topological_sort();

protected:
// For use by non-dynamic node constructors.
Expand Down Expand Up @@ -455,8 +464,12 @@ NodeType* Graph::emplace_node(Args&&... args) {
class ArrayNode : public Array, public virtual Node {};
class DecisionNode : public Decision, public virtual Node {
public:
/// Decision nodes by definition do not have a deterministic state.
bool deterministic_state() const final { return false; }
/// Set the current state to match the one at the time the given checkpoint was created.
virtual void assign_from_checkpoint(State& state, checkpoint_type& checkpoint) const = 0;
virtual void assign_from_checkpoint(State& state, checkpoint_type&& checkpoint) const = 0;

/// Get a checkpoint, an IOU that can be used to return the node to its current state.
virtual checkpoint_type checkpoint(State& state) const = 0;

/// Decision can only ever be equal to themselves because they are
/// independent variables.
Expand Down
3 changes: 2 additions & 1 deletion dwave/optimization/include/dwave-optimization/nodes.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023 D-Wave Systems Inc.
// Copyright 2023 D-Wave
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -28,5 +28,6 @@
#include "dwave-optimization/nodes/numbers.hpp"
#include "dwave-optimization/nodes/quadratic_model.hpp"
#include "dwave-optimization/nodes/reduce.hpp"
#include "dwave-optimization/nodes/set_routines.hpp"
#include "dwave-optimization/nodes/testing.hpp"
#include "dwave-optimization/nodes/unaryop.hpp"
Loading