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
24 changes: 12 additions & 12 deletions dwave/optimization/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
concatenate,
exp,
expit,
is_disjoint_cover,
logical_or,
maximum,
minimum,
Expand Down Expand Up @@ -377,15 +378,16 @@ class as the decision variable being optimized, with permutations of its
The :meth:`~dwave.optimization.model.Model.iter_decisions` method
obtains the decision variables of the generated model.

>>> routes = next(model.iter_decisions())
>>> routes = list(model.iter_decisions())

To test the solution above, set it in the model as the state of the
decision variable. **Skip these next lines** if you have submitted your
model to the `Leap <https://cloud.dwavesys.com/leap/>`_ :term:`hybrid`
nonlinear :term:`solver`.

>>> model.states.resize(1)
>>> routes.set_state(0, [[2., 7., 1., 5.], [4., 3., 8., 6., 0.]])
>>> routes[0].set_state(0, [2., 7., 1., 5.])
>>> routes[1].set_state(0, [4., 3., 8., 6., 0.])

You can use the :meth:`~dwave.optimization.model.Model.iter_constraints`
method to check feasibility of constructed or returned solutions. Here,
Expand All @@ -398,7 +400,7 @@ class as the decision variable being optimized, with permutations of its
... for i in range(model.states.size()):
... if capacity_constraint.state(i): # Filter on feasibility
... print((f"Objective value #{i} is {model.objective.state(i).round(2)} for routes\n"
... f" {[r.state(i).tolist() for r in routes.iter_successors()]}"))
... f" {[r.state(i).tolist() for r in routes]}"))
Objective value #0 is 423.8 for routes
[[2.0, 7.0, 1.0, 5.0], [4.0, 3.0, 8.0, 6.0, 0.0]]
"""
Expand Down Expand Up @@ -511,10 +513,8 @@ class as the decision variable being optimized, with permutations of its
demand = model.constant(customer_demand)
capacity = model.constant(vehicle_capacity)

# Add the decision variable
routes = model.disjoint_lists_symbol(
primary_set_size=num_customers,
num_disjoint_lists=number_of_vehicles)
routes = [model.list(num_customers, min_size=0) for _ in range(number_of_vehicles)]
model.add_constraint(is_disjoint_cover(routes))

# The objective is to minimize the distance traveled.
# This is calculated by adding the distance from the depot to the 1st customer
Expand Down Expand Up @@ -631,15 +631,16 @@ class as the decision variable being optimized, with permutations of its
The :meth:`~dwave.optimization.model.Model.iter_decisions` method
obtains the decision variables of the generated model.

>>> routes = next(model.iter_decisions())
>>> routes = list(model.iter_decisions())

To test the solution above, set it in the model as the state of the
decision variable. **Skip these next lines** if you have submitted your
model to the `Leap <https://cloud.dwavesys.com/leap/>`_ :term:`hybrid`
nonlinear :term:`solver`.

>>> model.states.resize(1)
>>> routes.set_state(0, [[0, 2], [1]])
>>> routes[0].set_state(0, [0, 2])
>>> routes[1].set_state(0, [1])

You can use the :meth:`~dwave.optimization.model.Model.iter_constraints`
method to check feasibility of constructed or returned solutions. Here,
Expand Down Expand Up @@ -757,9 +758,8 @@ class as the decision variable being optimized, with permutations of its
one = model.constant(1)

# Add the decision variable
routes = model.disjoint_lists_symbol(
primary_set_size=num_customers,
num_disjoint_lists=number_of_vehicles)
routes = [model.list(num_customers, min_size=0) for _ in range(number_of_vehicles)]
model.add_constraint(is_disjoint_cover(routes))

# Capacity constraint
capacity_constraints = [(demand[routes[vehicle_idx]].sum() <= capacity)
Expand Down
1 change: 1 addition & 0 deletions dwave/optimization/include/dwave-optimization/nodes.hpp
Original file line number Diff line number Diff line change
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"
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,45 @@

namespace dwave::optimization {

class IsDisjointCoverNode : public ScalarOutputMixin<EqualityMixin<ArrayNode>, false> {
public:
IsDisjointCoverNode(std::span<ArrayNode*> node_ptrs, ssize_t primary_set_size);

/// @copydoc Array::buff()
double const* buff(const State& state) const override;

/// @copydoc Node::commit()
void commit(State& state) const override;

/// @copydoc Array::diff()
std::span<const Update> diff(const State& state) const override;

/// @copydoc Node::initialize_state()
void initialize_state(State& state) const override;

/// @copydoc Array::integral()
bool integral() const override;

/// @copydoc Array::max()
double max() const override;

/// @copydoc Array::min()
double min() const override;

/// The size of the primary set to be covered
ssize_t primary_set_size() const { return primary_set_size_; };

/// @copydoc Node::propagate()
void propagate(State& state) const override;

/// @copydoc Node::revert()
void revert(State& state) const override;

private:
ssize_t primary_set_size_;
std::vector<Array*> operands_;
};

class IsInNode : public ArrayOutputMixin<EqualityMixin<ArrayNode>> {
public:
IsInNode(ArrayNode* element_ptr, ArrayNode* test_elements_ptr);
Expand Down
3 changes: 3 additions & 0 deletions dwave/optimization/libcpp/nodes/set_routines.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@ from dwave.optimization.libcpp.graph cimport ArrayNode


cdef extern from "dwave-optimization/nodes/set_routines.hpp" namespace "dwave::optimization" nogil:
cdef cppclass IsDisjointCoverNode(ArrayNode):
Py_ssize_t primary_set_size() const

cdef cppclass IsInNode(ArrayNode):
pass
48 changes: 48 additions & 0 deletions dwave/optimization/mathematical.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
Exp,
Expit,
Extract,
IsDisjointCover,
IsIn,
LessEqual,
LinearProgram,
Expand Down Expand Up @@ -93,6 +94,7 @@
"expit",
"extract",
"hstack",
"is_disjoint_cover",
"isin",
"less_equal",
"linprog",
Expand Down Expand Up @@ -1116,6 +1118,52 @@ def hstack(arrays: collections.abc.Sequence[ArraySymbol]) -> ArraySymbol:
return concatenate(arrays, 1)


def is_disjoint_cover(subsets: list[ArraySymbol], *, primary_set_size: int|None = None) -> IsDisjointCover:
"""Return whether the symbols are disjoint, set-like, and cover a set of integers.

Determines whether a collection of array symbols is disjoint and the union equals a fixed set.

Args:
subsets: List of array symbols to test whether they are disjoint and cover the primary set
primary_set_size: Number of elements in the primary set: {0, 1, ..., primary_set_size - 1}.
Must be non-negative. If `None`, primary set size is inferred from the common, finite,
nonnegative maximum value of the arrays.

Returns:
A scalar boolean-valued array symbol indicating whether the subsets are a disjoint cover

Examples:
>>> from dwave.optimization.model import Model
>>> from dwave.optimization.mathematical import is_disjoint_cover
...
>>> model = Model()
>>> model.states.resize(1)
>>> subsets = []
>>> subsets.append(model.constant([0, 1]))
>>> subsets.append(model.constant([2, 3, 4]))
>>> subsets.append(model.constant([]))
>>> is_disjoint = is_disjoint_cover(subsets, primary_set_size=5)
>>> with model.lock():
... print(is_disjoint.state(0))
1.0

See Also:
:class:`~dwave.optimization.symbols.IsDisjointCover`: Generated symbol

:func:`.isin`

.. versionadded:: 0.7.3
"""
if primary_set_size is None:
primary_set_size = int(subsets[0].info().max) + 1
if not np.isfinite(primary_set_size) or primary_set_size <= 0:
raise ValueError("Cannot infer primary set size from subsets")
for subset in subsets[1:]:
if int(subset.info().max) != primary_set_size - 1:
raise ValueError("Cannot infer primary set size from subsets")
return IsDisjointCover(subsets, primary_set_size)


def isin(element: ArraySymbol, test_elements: ArraySymbol) -> IsIn:
"""Return which values of one array symbol are in another.

Expand Down
9 changes: 9 additions & 0 deletions dwave/optimization/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,15 @@ def disjoint_lists_symbol(

:meth:`.iter_decisions`, :meth:`.iter_successors`
"""
warnings.warn(
"The use of Model.disjoint_lists_symbol() is deprecated "
"since dwave.optimization 0.7.3. Use\n"
"from dwave.optimization.mathematical import is_disjoint_cover\n"
"lists = [model.list(primary_set_size, min_size=0) for _ in range(num_disjoint_lists)]\n"
"model.add_constraint(is_disjoint_cover(primary_set_size, lists))",
DeprecationWarning,
)

from dwave.optimization.symbols import DisjointLists, DisjointList # avoid circular import
disjoint_lists = DisjointLists(self, primary_set_size, num_disjoint_lists)

Expand Down
Loading