diff --git a/science/py-dwave-optimization/Makefile b/science/py-dwave-optimization/Makefile index 6c9df0b43303..813a2a6d4546 100644 --- a/science/py-dwave-optimization/Makefile +++ b/science/py-dwave-optimization/Makefile @@ -1,31 +1,42 @@ PORTNAME= dwave-optimization -DISTVERSION= 0.7.1 -PORTREVISION= 3 +DISTVERSION= 0.7.2 CATEGORIES= science python # quantum-computing MASTER_SITES= PYPI PKGNAMEPREFIX= ${PYTHON_PKGNAMEPREFIX} DISTNAME= ${PORTNAME:S/-/_/}-${DISTVERSION} MAINTAINER= yuri@FreeBSD.org COMMENT= DWave: Nonlinear models for industrial optimization problems WWW= https://github.com/dwavesystems/dwave-optimization LICENSE= APACHE20 LICENSE_FILE= ${WRKSRC}/LICENSE -BUILD_DEPENDS= ${PYTHON_PKGNAMEPREFIX}numpy>=1.16:math/py-numpy@${PY_FLAVOR} \ - ${PYTHON_PKGNAMEPREFIX}meson-python>=0.18.0:devel/meson-python@${PY_FLAVOR} -RUN_DEPENDS= ${PYTHON_PKGNAMEPREFIX}numpy>=1.16:math/py-numpy@${PY_FLAVOR} +BUILD_DEPENDS= ${PYTHON_PKGNAMEPREFIX}meson-python>0:devel/meson-python@${PY_FLAVOR} \ + ${PYTHON_PKGNAMEPREFIX}numpy>=1.21.3:math/py-numpy@${PY_FLAVOR} +RUN_DEPENDS= ${PYTHON_PKGNAMEPREFIX}numpy>=1.21.3:math/py-numpy@${PY_FLAVOR} +TEST_DEPENDS= ${PYTHON_PKGNAMEPREFIX}dimod>=0.12.21:science/py-dimod@${PY_FLAVOR} \ + ${PYTHON_PKGNAMEPREFIX}scikit-learn>=1.6.0:science/py-scikit-learn@${PY_FLAVOR} USES= python USE_PYTHON= autoplist cython pep517 pytest +PYTEST_BROKEN_TESTS= test_simple_inputs + post-install: # strip binaries @${STRIP_CMD} \ ${STAGEDIR}${PYTHON_SITELIBDIR}/dwave/optimization/_model${PYTHON_TAG}.so \ ${STAGEDIR}${PYTHON_SITELIBDIR}/dwave/optimization/_utilities${PYTHON_TAG}.so \ ${STAGEDIR}${PYTHON_SITELIBDIR}/dwave/optimization/libdwave-optimization.so \ - ${STAGEDIR}${PYTHON_SITELIBDIR}/dwave/optimization/states${PYTHON_TAG}.so \ + ${STAGEDIR}${PYTHON_SITELIBDIR}/dwave/optimization/states${PYTHON_TAG}.so + +pre-test: + @cd ${STAGEDIR}${PYTHON_SITELIBDIR}/dwave/optimization && \ + ${FIND} . -name '*.so' -exec ${CP} {} ${WRKSRC}/dwave/optimization/{} \; + +# tests as of 0.7.2: 869 passed, 1 broken in 3.23s + +# all C++ patches are workaround for https://github.com/dwavesystems/dwave-optimization/issues/595 .include diff --git a/science/py-dwave-optimization/distinfo b/science/py-dwave-optimization/distinfo index ffa3fe497467..a2e87c68b9ae 100644 --- a/science/py-dwave-optimization/distinfo +++ b/science/py-dwave-optimization/distinfo @@ -1,3 +1,3 @@ -TIMESTAMP = 1781738751 -SHA256 (dwave_optimization-0.7.1.tar.gz) = 70085ae160b7692225392650924f137bc2c74f405d1a45d4e011aa647cb3ec03 -SIZE (dwave_optimization-0.7.1.tar.gz) = 520638 +TIMESTAMP = 1785188115 +SHA256 (dwave_optimization-0.7.2.tar.gz) = c8bd908c4714a0036a8710ad27840b5226c3b3cdd9f85f5176149b7db3a4ace3 +SIZE (dwave_optimization-0.7.2.tar.gz) = 535092 diff --git a/science/py-dwave-optimization/files/patch-dwave_optimization__model.pyx b/science/py-dwave-optimization/files/patch-dwave_optimization__model.pyx new file mode 100644 index 000000000000..352cb53479e2 --- /dev/null +++ b/science/py-dwave-optimization/files/patch-dwave_optimization__model.pyx @@ -0,0 +1,56 @@ +-- Use the mangled C++ type name as the runtime type registry key instead of +-- std::type_index. On FreeBSD/Clang libc++ compares type_info object addresses +-- by default, and the same class template instantiations are emitted into both +-- libdwave-optimization.so and each Cython extension module. The resulting +-- distinct type_info objects for the same type cause registry lookups to fail, +-- producing "given pointer cannot be cast to a known node type". Comparing +-- mangled type name strings works across DSO boundaries. + +--- dwave/optimization/_model.pyx.orig 2026-07-23 12:58:12 UTC ++++ dwave/optimization/_model.pyx +@@ -32,7 +32,7 @@ + from libcpp cimport bool + from libcpp.memory cimport make_shared + from libcpp.span cimport span +-from libcpp.typeindex cimport type_index ++from libcpp.string cimport string + from libcpp.unordered_map cimport unordered_map + from libcpp.utility cimport move + from libcpp.vector cimport vector +@@ -65,17 +65,22 @@ + """A tuple of 2-tuples listing all serialization versions supported.""" + + +-# Store a mapping from the type_index of each C++ Node type to the relevant +-# Cython class. We don't refcount the PyObject*s pointing to each class because +-# the lifespace of this map is identical to that of the type objects. +-cdef unordered_map[type_index, PyObject*] _cpp_type_to_python ++# Store a mapping from the mangled C++ type name of each Node type to the ++# relevant Cython class. We use the type name string rather than std::type_index ++# because libc++'s default type_info comparison compares object addresses, and ++# the same C++ class template instantiations are emitted into both ++# libdwave-optimization.so and the individual Cython extension modules, giving ++# distinct type_info objects for the same type across DSOs on FreeBSD/Clang. ++# We don't refcount the PyObject*s pointing to each class because the lifespan of ++# this map is identical to that of the type objects. ++cdef unordered_map[string, PyObject*] _cpp_type_to_python + + # Register a mapping between the given Cython class and C++ class. + cdef void _register(object cls, const type_info& typeinfo): + """Register a Python/Cython symbol to allow it to be created from a pointer + via `symbol_from_ptr`. + """ +- _cpp_type_to_python[type_index(typeinfo)] = (cls) ++ _cpp_type_to_python[string(typeinfo.name())] = (cls) + + + cdef object symbol_from_ptr(_Graph model, cppNode* node_ptr): +@@ -86,7 +91,7 @@ + raise ValueError("cannot construct a Symbol from the given pointer") + + try: +- cls = _cpp_type_to_python.at(type_index(typeid(deref(node_ptr)))) ++ cls = _cpp_type_to_python.at(string(typeid(deref(node_ptr)).name())) + except IndexError: + # IndexError would be returned by .at() + raise RuntimeError("given pointer cannot be cast to a known node type") from None diff --git a/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_array.hpp b/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_array.hpp new file mode 100644 index 000000000000..f56826b81f7c --- /dev/null +++ b/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_array.hpp @@ -0,0 +1,10 @@ +--- dwave/optimization/include/dwave-optimization/array.hpp.orig 2026-07-27 22:30:07 UTC ++++ dwave/optimization/include/dwave-optimization/array.hpp +@@ -371,6 +371,7 @@ class Array { + /// the Array::dynamic() method is provided. + class Array { + public: ++ virtual ~Array(); + /// A std::random_access_iterator over the values in the array. + using iterator = BufferIterator; + diff --git a/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_graph.hpp b/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_graph.hpp new file mode 100644 index 000000000000..a942ca5ccb89 --- /dev/null +++ b/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_graph.hpp @@ -0,0 +1,23 @@ +--- dwave/optimization/include/dwave-optimization/graph.hpp.orig 2026-07-27 22:30:07 UTC ++++ dwave/optimization/include/dwave-optimization/graph.hpp +@@ -254,7 +254,7 @@ class Node { + }; + + Node() noexcept : expired_ptr_(new bool(false)) {} +- virtual ~Node() { *expired_ptr_ = true; } ++ virtual ~Node(); + + // Nodes cannot be moved or copied. + Node(const Node&) = delete; +@@ -452,7 +452,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: ++ ~ArrayNode() override; ++}; + class DecisionNode : public Decision, public virtual Node { + public: + /// Decision nodes by definition do not have a deterministic state. diff --git a/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_nodes_binaryop.hpp b/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_nodes_binaryop.hpp new file mode 100644 index 000000000000..6f107c0cecb0 --- /dev/null +++ b/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_nodes_binaryop.hpp @@ -0,0 +1,34 @@ +-- Declare explicit template instantiations for BinaryOpNode specializations so +-- they are defined only in libdwave-optimization.so (dwave/optimization/src/nodes/binaryop.cpp). +-- Without this, each Cython extension module that creates a binary-op node +-- instantiates its own copy of the class template, producing a distinct type_info +-- object for the same type. libc++'s default type_info comparison then treats +-- those copies as different types and cross-DSO dynamic_cast fails on +-- FreeBSD/Clang. + +--- dwave/optimization/include/dwave-optimization/nodes/binaryop.hpp.orig 2026-07-23 12:58:12 UTC ++++ dwave/optimization/include/dwave-optimization/nodes/binaryop.hpp +@@ -106,4 +106,23 @@ + using SubtractNode = BinaryOpNode>; + using XorNode = BinaryOpNode>; + ++// Explicit instantiations live in dwave/optimization/src/nodes/binaryop.cpp so ++// that libdwave-optimization.so owns the canonical type_info objects. Tell ++// other translation units (especially the Cython extension modules) not to ++// emit their own copies, which would break cross-DSO dynamic_cast on ++// FreeBSD/Clang. ++extern template class BinaryOpNode>; ++extern template class BinaryOpNode>; ++extern template class BinaryOpNode>; ++extern template class BinaryOpNode>; ++extern template class BinaryOpNode>; ++extern template class BinaryOpNode>; ++extern template class BinaryOpNode>; ++extern template class BinaryOpNode>; ++extern template class BinaryOpNode>; ++extern template class BinaryOpNode>; ++extern template class BinaryOpNode>; ++extern template class BinaryOpNode>; ++extern template class BinaryOpNode>; ++ + } // namespace dwave::optimization diff --git a/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_nodes_lp.hpp b/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_nodes_lp.hpp new file mode 100644 index 000000000000..55123382dbfd --- /dev/null +++ b/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_nodes_lp.hpp @@ -0,0 +1,53 @@ +-- Make the LP node destructors out-of-line so their type_info objects are +-- emitted as strong (GLOBAL) symbols in libdwave-optimization.so. Without +-- this, the Cython lp.so module emits its own copies of the polymorphic base +-- LinearProgramNodeBase type_info, which breaks cross-DSO dynamic_cast from +-- LinearProgramNode to LinearProgramNodeBase on FreeBSD/Clang. + +--- dwave/optimization/include/dwave-optimization/nodes/lp.hpp.orig 2026-07-23 12:58:12 UTC ++++ dwave/optimization/include/dwave-optimization/nodes/lp.hpp +@@ -27,6 +27,8 @@ + /// A logical node that propagates whether or not its predecessor LinearProgram is feasible. + class LinearProgramFeasibleNode : public ScalarOutputMixin, true> { + public: ++ virtual ~LinearProgramFeasibleNode(); ++ + explicit LinearProgramFeasibleNode(LinearProgramNodeBase* lp_ptr); + + /// @copydoc Node::initialize_state() +@@ -53,6 +55,8 @@ + + class LinearProgramNodeBase : public Node { + public: ++ virtual ~LinearProgramNodeBase(); ++ + /// The default lower bound for variables + static const double default_lower_bound(); + +@@ -102,6 +106,8 @@ + /// callback=None, options=None, x0=None, integrality=None) + class LinearProgramNode : public EqualityMixin { + public: ++ virtual ~LinearProgramNode(); ++ + /// Construct a LinearProgramNode + /// + /// Note: parameter names are chosen to match scipy.optimize.lingprog() +@@ -194,6 +200,8 @@ + /// LinearProgramNode. Note that the output is undefined if the solution is not feasible. + class LinearProgramObjectiveValueNode : public ScalarOutputMixin, true> { + public: ++ virtual ~LinearProgramObjectiveValueNode(); ++ + explicit LinearProgramObjectiveValueNode(LinearProgramNodeBase* lp_ptr); + + /// @copydoc Node::initialize_state() +@@ -222,6 +230,8 @@ + /// solution may not be feasible or optimial. + class LinearProgramSolutionNode : public ArrayOutputMixin> { + public: ++ virtual ~LinearProgramSolutionNode(); ++ + explicit LinearProgramSolutionNode(LinearProgramNodeBase* lp_ptr); + + /// @copydoc Array::buff() diff --git a/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_nodes_naryop.hpp b/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_nodes_naryop.hpp new file mode 100644 index 000000000000..0f8d99f6a4e8 --- /dev/null +++ b/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_nodes_naryop.hpp @@ -0,0 +1,20 @@ +-- Declare explicit template instantiations for NaryOpNode specializations so +-- they are defined only in libdwave-optimization.so (dwave/optimization/src/nodes/naryop.cpp). +-- This prevents each Cython extension module from emitting its own type_info +-- copy, which breaks cross-DSO dynamic_cast on FreeBSD/Clang. + +--- dwave/optimization/include/dwave-optimization/nodes/naryop.hpp.orig 2026-07-23 12:58:12 UTC ++++ dwave/optimization/include/dwave-optimization/nodes/naryop.hpp +@@ -86,4 +86,12 @@ + using NaryMinimumNode = NaryOpNode>; + using NaryMultiplyNode = NaryOpNode>; + ++// Explicit instantiations live in dwave/optimization/src/nodes/naryop.cpp so ++// that libdwave-optimization.so owns the canonical type_info objects. Tell ++// other translation units not to emit their own copies. ++extern template class NaryOpNode>; ++extern template class NaryOpNode>; ++extern template class NaryOpNode>; ++extern template class NaryOpNode>; ++ + } // namespace dwave::optimization diff --git a/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_nodes_reduce.hpp b/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_nodes_reduce.hpp new file mode 100644 index 000000000000..5c1f97e00436 --- /dev/null +++ b/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_nodes_reduce.hpp @@ -0,0 +1,22 @@ +-- Declare explicit template instantiations for ReduceNode specializations so +-- they are defined only in libdwave-optimization.so (dwave/optimization/src/nodes/reduce.cpp). +-- This prevents each Cython extension module from emitting its own type_info +-- copy, which breaks cross-DSO dynamic_cast on FreeBSD/Clang. + +--- dwave/optimization/include/dwave-optimization/nodes/reduce.hpp.orig 2026-07-23 12:58:12 UTC ++++ dwave/optimization/include/dwave-optimization/nodes/reduce.hpp +@@ -141,4 +141,14 @@ + using ProdNode = ReduceNode>; + using SumNode = ReduceNode>; + ++// Explicit instantiations live in dwave/optimization/src/nodes/reduce.cpp so ++// that libdwave-optimization.so owns the canonical type_info objects. Tell ++// other translation units not to emit their own copies. ++extern template class ReduceNode>; ++extern template class ReduceNode>; ++extern template class ReduceNode>; ++extern template class ReduceNode>; ++extern template class ReduceNode>; ++extern template class ReduceNode>; ++ + } // namespace dwave::optimization diff --git a/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_nodes_unaryop.hpp b/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_nodes_unaryop.hpp new file mode 100644 index 000000000000..45873043e3e2 --- /dev/null +++ b/science/py-dwave-optimization/files/patch-dwave_optimization_include_dwave-optimization_nodes_unaryop.hpp @@ -0,0 +1,29 @@ +-- Declare explicit template instantiations for UnaryOpNode specializations so +-- they are defined only in libdwave-optimization.so (dwave/optimization/src/nodes/unaryop.cpp). +-- This prevents each Cython extension module from emitting its own type_info +-- copy, which breaks cross-DSO dynamic_cast on FreeBSD/Clang. + +--- dwave/optimization/include/dwave-optimization/nodes/unaryop.hpp.orig 2026-07-23 12:58:12 UTC ++++ dwave/optimization/include/dwave-optimization/nodes/unaryop.hpp +@@ -96,4 +96,21 @@ + using SquareRootNode = UnaryOpNode>; + using TanhNode = UnaryOpNode>; + ++// Explicit instantiations live in dwave/optimization/src/nodes/unaryop.cpp so ++// that libdwave-optimization.so owns the canonical type_info objects. Tell ++// other translation units not to emit their own copies. ++extern template class UnaryOpNode>; ++extern template class UnaryOpNode>; ++extern template class UnaryOpNode>; ++extern template class UnaryOpNode>; ++extern template class UnaryOpNode>; ++extern template class UnaryOpNode>; ++extern template class UnaryOpNode>; ++extern template class UnaryOpNode>; ++extern template class UnaryOpNode>; ++extern template class UnaryOpNode>; ++extern template class UnaryOpNode>; ++extern template class UnaryOpNode>; ++extern template class UnaryOpNode>; ++ + } // namespace dwave::optimization diff --git a/science/py-dwave-optimization/files/patch-dwave_optimization_src_graph.cpp b/science/py-dwave-optimization/files/patch-dwave_optimization_src_graph.cpp new file mode 100644 index 000000000000..1dba5bb43e27 --- /dev/null +++ b/science/py-dwave-optimization/files/patch-dwave_optimization_src_graph.cpp @@ -0,0 +1,13 @@ +--- dwave/optimization/src/graph.cpp.orig 2026-07-27 22:30:07 UTC ++++ dwave/optimization/src/graph.cpp +@@ -33,6 +33,10 @@ namespace dwave::optimization { + + namespace dwave::optimization { + ++Node::~Node() { *expired_ptr_ = true; } ++Array::~Array() = default; ++ArrayNode::~ArrayNode() = default; ++ + void Graph::add_constraint(ArrayNode* constraint_ptr) { + if (not constraint_ptr->logical()) { + throw std::invalid_argument("constraint must have a logical output"); diff --git a/science/py-dwave-optimization/files/patch-dwave_optimization_src_nodes_lp.cpp b/science/py-dwave-optimization/files/patch-dwave_optimization_src_nodes_lp.cpp new file mode 100644 index 000000000000..72aae7804fb8 --- /dev/null +++ b/science/py-dwave-optimization/files/patch-dwave_optimization_src_nodes_lp.cpp @@ -0,0 +1,20 @@ +-- Define out-of-line destructors for the LP node classes so their type_info +-- objects are emitted as strong (GLOBAL) symbols in libdwave-optimization.so, +-- matching the header declarations. This fixes cross-DSO dynamic_cast failures +-- on FreeBSD/Clang where each shared library otherwise gets its own copy of the +-- base LinearProgramNodeBase type_info. + +--- dwave/optimization/src/nodes/lp.cpp.orig 2026-07-23 12:58:12 UTC ++++ dwave/optimization/src/nodes/lp.cpp +@@ -22,6 +22,12 @@ + #include "dwave-optimization/graph.hpp" + + namespace dwave::optimization { ++ ++LinearProgramFeasibleNode::~LinearProgramFeasibleNode() = default; ++LinearProgramNodeBase::~LinearProgramNodeBase() = default; ++LinearProgramNode::~LinearProgramNode() = default; ++LinearProgramObjectiveValueNode::~LinearProgramObjectiveValueNode() = default; ++LinearProgramSolutionNode::~LinearProgramSolutionNode() = default; + + static constexpr double FEASIBILITY_TOLERANCE = 1e-07; diff --git a/science/py-dwave-optimization/files/patch-meson.build b/science/py-dwave-optimization/files/patch-meson.build new file mode 100644 index 000000000000..42ee1f84c6ee --- /dev/null +++ b/science/py-dwave-optimization/files/patch-meson.build @@ -0,0 +1,28 @@ +-- Force libc++ to use name-based type_info comparison. On FreeBSD/Clang the +-- default compares type_info object addresses, but the same C++ class +-- template instantiations are emitted into both libdwave-optimization.so and +-- each Cython extension module, producing distinct type_info objects for the +-- same type. This breaks cross-DSO std::type_index lookups and dynamic_cast. +-- The "non-unique" implementation compares mangled type names, so types match +-- across shared libraries. + +--- meson.build.orig 2026-07-23 19:58:12 UTC ++++ meson.build +@@ -22,6 +22,17 @@ add_project_arguments(['-g1'], language: ['cpp']) + # We want some debugging symbols + add_project_arguments(['-g1'], language: ['cpp']) + ++# On FreeBSD/Clang libc++ defaults to comparing type_info object addresses. The ++# same C++ class template instantiations are emitted into both ++# libdwave-optimization.so and the individual Cython extension modules, so ++# each shared object gets its own type_info copy and cross-DSO dynamic_cast ++# and std::type_index lookups fail. Force the "non-unique" implementation, ++# which compares mangled type names, so types match across shared libraries. ++add_project_arguments( ++ '-D_LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION=2', ++ language: 'cpp', ++) ++ + cpp = meson.get_compiler('cpp') + py = import('python').find_installation(pure: false) +