11/*
22 Pybind11 module exposing NetGraph-Core C++ APIs to Python.
3-
4- Notes:
5- - Accepts NumPy arrays (C-contiguous) and converts to spans for zero-copy
6- views where possible.
7- - Distances returned as float64 arrays with inf for unreachable.
8- - Edge/Node masks are validated for dtype=bool and length.
3+ - Uses NumPy arrays with zero-copy spans where possible
4+ - Returns distances as float64 arrays (inf for unreachable)
5+ - Validates edge/node masks for bool dtype and length
6+ - Array-returning view methods expose non-owning buffers over internal state; treat as read-only
97*/
108#include < pybind11/pybind11.h>
119#include < pybind11/numpy.h>
@@ -82,13 +80,12 @@ PYBIND11_MODULE(_netgraph_core, m) {
8280 auto dst_s = as_span<std::int32_t >(dst, " dst" );
8381 if (src_s.size () != dst_s.size ()) throw py::type_error (" src and dst must have the same length" );
8482 auto cap_s = as_span<double >(capacity, " capacity" );
85- // Require cost dtype to be int64 for consistency
83+ // Cost dtype must be int64 to match internal Cost type
8684 auto cost_s = as_span<std::int64_t >(cost, " cost" );
87- std::span<const Cost> cost_cs (reinterpret_cast <const Cost*>(cost_s.data ()), cost_s.size ());
8885 return StrictMultiDiGraph::from_arrays (num_nodes,
8986 src_s,
9087 dst_s,
91- cap_s, cost_cs , add_reverse);
88+ cap_s, cost_s , add_reverse);
9289 },
9390 py::arg (" num_nodes" ), py::arg (" src" ), py::arg (" dst" ), py::arg (" capacity" ), py::arg (" cost" ),
9491 py::kw_only (), py::arg (" add_reverse" ) = false )
@@ -97,7 +94,7 @@ PYBIND11_MODULE(_netgraph_core, m) {
9794 // external link ids removed; EdgeId is the canonical id
9895 .def (" capacity_view" , [](py::object self_obj, const StrictMultiDiGraph& g){
9996 auto s = g.capacity_view ();
100- return py::array (
97+ py::array out (
10198 py::buffer_info (
10299 const_cast <double *>(s.data ()),
103100 sizeof (double ),
@@ -108,10 +105,11 @@ PYBIND11_MODULE(_netgraph_core, m) {
108105 ),
109106 self_obj
110107 );
108+ return out;
111109 })
112110 .def (" edge_src_view" , [](py::object self_obj, const StrictMultiDiGraph& g){
113111 auto s = g.edge_src_view ();
114- return py::array (
112+ py::array out (
115113 py::buffer_info (
116114 const_cast <std::int32_t *>(s.data ()),
117115 sizeof (std::int32_t ),
@@ -122,10 +120,11 @@ PYBIND11_MODULE(_netgraph_core, m) {
122120 ),
123121 self_obj
124122 );
123+ return out;
125124 })
126125 .def (" edge_dst_view" , [](py::object self_obj, const StrictMultiDiGraph& g){
127126 auto s = g.edge_dst_view ();
128- return py::array (
127+ py::array out (
129128 py::buffer_info (
130129 const_cast <std::int32_t *>(s.data ()),
131130 sizeof (std::int32_t ),
@@ -136,10 +135,11 @@ PYBIND11_MODULE(_netgraph_core, m) {
136135 ),
137136 self_obj
138137 );
138+ return out;
139139 })
140140 .def (" cost_view" , [](py::object self_obj, const StrictMultiDiGraph& g){
141141 auto s = g.cost_view ();
142- return py::array (
142+ py::array out (
143143 py::buffer_info (
144144 const_cast <Cost*>(s.data ()),
145145 sizeof (Cost),
@@ -150,6 +150,7 @@ PYBIND11_MODULE(_netgraph_core, m) {
150150 ),
151151 self_obj
152152 );
153+ return out;
153154 })
154155 .def (" row_offsets_view" , [](const StrictMultiDiGraph& g){
155156 auto s = g.row_offsets_view ();
@@ -446,24 +447,18 @@ PYBIND11_MODULE(_netgraph_core, m) {
446447 })
447448 .def (" capacity_view" , [](py::object self_obj, const FlowState& fs){
448449 auto s = fs.capacity_view ();
449- return py::array (
450- py::buffer_info (
451- const_cast <double *>(s.data ()), sizeof (double ), py::format_descriptor<double >::format (), 1 , { s.size () }, { sizeof (double ) }
452- ), self_obj);
450+ py::array out (py::buffer_info (const_cast <double *>(s.data ()), sizeof (double ), py::format_descriptor<double >::format (), 1 , { s.size () }, { sizeof (double ) }), self_obj);
451+ return out;
453452 })
454453 .def (" residual_view" , [](py::object self_obj, const FlowState& fs){
455454 auto s = fs.residual_view ();
456- return py::array (
457- py::buffer_info (
458- const_cast <double *>(s.data ()), sizeof (double ), py::format_descriptor<double >::format (), 1 , { s.size () }, { sizeof (double ) }
459- ), self_obj);
455+ py::array out (py::buffer_info (const_cast <double *>(s.data ()), sizeof (double ), py::format_descriptor<double >::format (), 1 , { s.size () }, { sizeof (double ) }), self_obj);
456+ return out;
460457 })
461458 .def (" edge_flow_view" , [](py::object self_obj, const FlowState& fs){
462459 auto s = fs.edge_flow_view ();
463- return py::array (
464- py::buffer_info (
465- const_cast <double *>(s.data ()), sizeof (double ), py::format_descriptor<double >::format (), 1 , { s.size () }, { sizeof (double ) }
466- ), self_obj);
460+ py::array out (py::buffer_info (const_cast <double *>(s.data ()), sizeof (double ), py::format_descriptor<double >::format (), 1 , { s.size () }, { sizeof (double ) }), self_obj);
461+ return out;
467462 })
468463 .def (" place_on_dag" , [](FlowState& fs, std::int32_t src, std::int32_t dst, const PredDAG& dag, double requested_flow, FlowPlacement placement, bool shortest_path){
469464 py::gil_scoped_release rel; auto placed = fs.place_on_dag (src, dst, dag, requested_flow, placement, shortest_path); py::gil_scoped_acquire acq; return placed;
@@ -630,8 +625,8 @@ PYBIND11_MODULE(_netgraph_core, m) {
630625 py::arg (" diminishing_returns_epsilon_frac" ) = 1e-3 )
631626 .def (" flow_count" , &FlowPolicy::flow_count)
632627 .def (" placed_demand" , &FlowPolicy::placed_demand)
633- .def (" place_demand" , [](FlowPolicy& p, FlowGraph& fg, std::int32_t src, std::int32_t dst, std:: int32_t flowClass, double volume, py::object target_per_flow, py::object min_flow){ std::optional<double > tpf; if (!target_per_flow.is_none ()) tpf = py::cast<double >(target_per_flow); std::optional<double > mfl; if (!min_flow.is_none ()) mfl = py::cast<double >(min_flow); py::gil_scoped_release rel; auto pr = p.place_demand (fg, src, dst, flowClass, volume, tpf, mfl); py::gil_scoped_acquire acq; return py::make_tuple (pr.first , pr.second ); }, py::arg (" flow_graph" ), py::arg (" src" ), py::arg (" dst" ), py::arg (" flowClass" ), py::arg (" volume" ), py::arg (" target_per_flow" ) = py::none (), py::arg (" min_flow" ) = py::none ())
634- .def (" rebalance_demand" , [](FlowPolicy& p, FlowGraph& fg, std::int32_t src, std::int32_t dst, std:: int32_t flowClass, double target){ py::gil_scoped_release rel; auto pr = p.rebalance_demand (fg, src, dst, flowClass, target); py::gil_scoped_acquire acq; return py::make_tuple (pr.first , pr.second ); },
628+ .def (" place_demand" , [](FlowPolicy& p, FlowGraph& fg, std::int32_t src, std::int32_t dst, FlowClass flowClass, double volume, py::object target_per_flow, py::object min_flow){ std::optional<double > tpf; if (!target_per_flow.is_none ()) tpf = py::cast<double >(target_per_flow); std::optional<double > mfl; if (!min_flow.is_none ()) mfl = py::cast<double >(min_flow); py::gil_scoped_release rel; auto pr = p.place_demand (fg, src, dst, flowClass, volume, tpf, mfl); py::gil_scoped_acquire acq; return py::make_tuple (pr.first , pr.second ); }, py::arg (" flow_graph" ), py::arg (" src" ), py::arg (" dst" ), py::arg (" flowClass" ), py::arg (" volume" ), py::arg (" target_per_flow" ) = py::none (), py::arg (" min_flow" ) = py::none ())
629+ .def (" rebalance_demand" , [](FlowPolicy& p, FlowGraph& fg, std::int32_t src, std::int32_t dst, FlowClass flowClass, double target){ py::gil_scoped_release rel; auto pr = p.rebalance_demand (fg, src, dst, flowClass, target); py::gil_scoped_acquire acq; return py::make_tuple (pr.first , pr.second ); },
635630 py::arg (" flow_graph" ), py::arg (" src" ), py::arg (" dst" ), py::arg (" flowClass" ), py::arg (" target" ))
636631 .def (" remove_demand" , [](FlowPolicy& p, FlowGraph& fg){ py::gil_scoped_release rel; p.remove_demand (fg); py::gil_scoped_acquire acq; })
637632 .def_property_readonly (" flows" , [](const FlowPolicy& p){ py::dict out; for (auto const & kv : p.flows ()) { const auto & idx = kv.first ; const auto & f = kv.second ; out[py::make_tuple (idx.src , idx.dst , idx.flowClass , idx.flowId )] = py::make_tuple (f.src , f.dst , f.cost , f.placed_flow ); } return out; });
0 commit comments