@@ -34,7 +34,7 @@ from libcpp.unordered_map cimport unordered_map
3434from libcpp.utility cimport move
3535from libcpp.vector cimport vector
3636
37- from dwave.optimization.libcpp cimport get, holds_alternative, span
37+ from dwave.optimization.libcpp cimport bind_front, get, holds_alternative, span
3838from dwave.optimization.libcpp.array cimport (
3939 Array as cppArray,
4040 SizeInfo as cppSizeInfo,
@@ -108,6 +108,7 @@ from dwave.optimization.libcpp.nodes cimport (
108108 SumNode as cppSumNode,
109109 WhereNode as cppWhereNode,
110110 XorNode as cppXorNode,
111+ ZephyrNode as cppZephyrNode,
111112 )
112113from dwave.optimization.model cimport ArraySymbol, _Graph, Symbol
113114from dwave.optimization.states cimport States
@@ -172,6 +173,7 @@ __all__ = [
172173 " Sum" ,
173174 " Where" ,
174175 " Xor" ,
176+ " Zephyr" ,
175177 ]
176178
177179# We would like to be able to do constructions like dynamic_cast[cppConstantNode*](...)
@@ -4003,3 +4005,110 @@ cdef class Xor(ArraySymbol):
40034005 cdef cppXorNode* ptr
40044006
40054007_register(Xor, typeid(cppXorNode))
4008+
4009+
4010+ cdef double _read_linear(void * obj, int v) noexcept nogil:
4011+ with gil:
4012+ try :
4013+ return (< object > obj)(v)
4014+ except Exception as ex:
4015+ (< object > obj).ex = ex
4016+ # If we got here that means an exception was raised. So we trigger C++'s
4017+ # error handling by returning a non-finite value.
4018+ return float (' nan' )
4019+
4020+ cdef double _read_quadratic(void * obj, int u, int v) noexcept nogil:
4021+ # object must be a Python callable!
4022+ with gil:
4023+ try :
4024+ return (< object > obj)(u, v)
4025+ except Exception as ex:
4026+ (< object > obj).ex = ex
4027+ # If we got here that means an exception was raised. So we trigger C++'s
4028+ # error handling by returning a non-finite value.
4029+ return float (' nan' )
4030+
4031+ # Currently we don't have a matching dwave.optimization.mathematical function
4032+ # so we document it better here than we usually do.
4033+ cdef class Zephyr(ArraySymbol):
4034+ """ A quadratic model with biases arranged according to a Zephyr lattice.
4035+
4036+ Encode a quadratic model arranged according to a :term:`Zephyr` lattice.
4037+
4038+ Args:
4039+ x: A 1D array symbol giving the assignments to the variables.
4040+ m: Grid parameter for the Zephyr lattice.
4041+ linear:
4042+ A function that will be called for each node in the Zephyr lattice.
4043+ Must accept a single ``int`` giving the node index.
4044+ Must return a finite number giving the linear bias.
4045+ Will be called once for each node.
4046+ quadratic:
4047+ A function that will be called for each edge in the Zephyr lattice.
4048+ Must accept two ``int`` giving the indices of the nodes that share an edge.
4049+ Must return a finite number giving the quadratic bias.
4050+ Will be called once for each edge.
4051+
4052+ See also:
4053+ :func:`dwave_networkx.zephyr_graph()`
4054+
4055+ ..versionadded:: 0.6.2
4056+ """
4057+ def __init__ (self , ArraySymbol x , Py_ssize_t m , *, object linear , object quadratic ):
4058+ cdef _Graph model = x.model
4059+
4060+ # todo: we could accept dictionaries directly (probably adding uv + vu for quadratic)
4061+ if not callable (linear):
4062+ raise TypeError (" linear must be a callable that accepts one int and returns a float" )
4063+ if not callable (quadratic):
4064+ raise TypeError (" quadratic must be a callable that accepts two ints and returns a float" )
4065+
4066+ # We have to do a bit of convolution in order to do error handling
4067+ # We start by making some aliases of our input functions so that we
4068+ # can add an .ex attribute that will signal if an exception was raised
4069+ # in the C++ code.
4070+ def _linear (v ):
4071+ return linear(v)
4072+ _linear.ex = None
4073+ def _quadratic (u , v ):
4074+ return quadratic(u, v)
4075+ _quadratic.ex = None
4076+
4077+ try :
4078+ self .ptr = model._graph.emplace_node[cppZephyrNode](
4079+ x.array_ptr,
4080+ m,
4081+ bind_front(_read_linear, < void * > _linear),
4082+ bind_front(_read_quadratic, < void * > _quadratic),
4083+ )
4084+ except Exception as ex:
4085+ # Determine if the error was raised by the Python function or by C++
4086+ if _linear.ex:
4087+ raise _linear.ex from None
4088+ if _quadratic.ex:
4089+ raise _quadratic.ex from None
4090+ raise ex
4091+
4092+ self .initialize_arraynode(model, self .ptr)
4093+
4094+ @staticmethod
4095+ def lattice_num_edges (Py_ssize_t m ):
4096+ """ Return the number of edges in a Zephyr lattice with grid parameter ``m``."""
4097+ return cppZephyrNode.lattice_num_edges(m)
4098+
4099+ @staticmethod
4100+ def lattice_num_nodes (Py_ssize_t m ):
4101+ """ Return the number of nodes in a Zephyr lattice with grid parameter ``m``."""
4102+ return cppZephyrNode.lattice_num_nodes(m)
4103+
4104+ def linear (self , Py_ssize_t v ):
4105+ """ Return the linear bias associated with node ``v``, or ``0`` if the node
4106+ is not in the model."""
4107+ return self .ptr.linear(v)
4108+
4109+ def quadratic (self , Py_ssize_t u , Py_ssize_t v ):
4110+ """ Return the quadratic bias associated with edge ``u, v``, or ``0`` if
4111+ the edge is not in the model."""
4112+ return self .ptr.quadratic(u, v)
4113+
4114+ cdef cppZephyrNode* ptr
0 commit comments