@@ -31,7 +31,11 @@ PYBIND11_MODULE(_netgraph_core, m) {
3131 py::enum_<EdgeSelect>(m, " EdgeSelect" )
3232 .value (" ALL_MIN_COST" , EdgeSelect::AllMinCost)
3333 .value (" SINGLE_MIN_COST" , EdgeSelect::SingleMinCost)
34- .value (" ALL_MIN_COST_WITH_CAP_REMAINING" , EdgeSelect::AllMinCostWithCapRemaining);
34+ .value (" ALL_MIN_COST_WITH_CAP_REMAINING" , EdgeSelect::AllMinCostWithCapRemaining)
35+ .value (" ALL_ANY_COST_WITH_CAP_REMAINING" , EdgeSelect::AllAnyCostWithCapRemaining)
36+ .value (" SINGLE_MIN_COST_WITH_CAP_REMAINING" , EdgeSelect::SingleMinCostWithCapRemaining)
37+ .value (" SINGLE_MIN_COST_WITH_CAP_REMAINING_LOAD_FACTORED" , EdgeSelect::SingleMinCostWithCapRemainingLoadFactored)
38+ .value (" USER_DEFINED" , EdgeSelect::UserDefined);
3539
3640 py::enum_<FlowPlacement>(m, " FlowPlacement" )
3741 .value (" PROPORTIONAL" , FlowPlacement::Proportional)
@@ -176,19 +180,6 @@ PYBIND11_MODULE(_netgraph_core, m) {
176180 return py::make_tuple (std::move (dist_arr), res.second );
177181 }, py::arg (" g" ), py::arg (" src" ), py::arg (" dst" ) = py::none (), py::kw_only (), py::arg (" edge_select" ) = EdgeSelect::AllMinCost, py::arg (" multipath" ) = true , py::arg (" node_mask" ) = py::none (), py::arg (" edge_mask" ) = py::none (), py::arg (" eps" ) = 1e-10 );
178182
179- py::class_<Path>(m, " Path" )
180- .def_property_readonly (" nodes" , [](const Path& p){
181- py::array_t <std::int32_t > arr (p.nodes .size ());
182- std::memcpy (arr.mutable_data (), p.nodes .data (), p.nodes .size ()*sizeof (std::int32_t ));
183- return arr;
184- })
185- .def_property_readonly (" edges" , [](const Path& p){
186- py::array_t <std::int32_t > arr (p.edges .size ());
187- std::memcpy (arr.mutable_data (), p.edges .data (), p.edges .size ()*sizeof (std::int32_t ));
188- return arr;
189- })
190- .def_readonly (" cost" , &Path::cost);
191-
192183 m.def (" ksp" ,
193184 [](const StrictMultiDiGraph& g, std::int32_t src, std::int32_t dst,
194185 int k, py::object max_cost_factor, bool unique, py::object node_mask, py::object edge_mask, double eps) {
@@ -209,9 +200,17 @@ PYBIND11_MODULE(_netgraph_core, m) {
209200 if (static_cast <std::size_t >(buf.shape [0 ]) != static_cast <std::size_t >(g.num_edges ())) throw py::type_error (" edge_mask length must equal num_edges" );
210201 }
211202 py::gil_scoped_release release;
212- auto paths = k_shortest_paths (g, src, dst, k, mcf, unique, eps);
203+ auto items = k_shortest_paths (g, src, dst, k, mcf, unique, eps);
213204 py::gil_scoped_acquire acquire;
214- return paths;
205+ // Convert to list of (dist ndarray, PredDAG)
206+ py::list out;
207+ for (auto & pr : items) {
208+ auto & dist = pr.first ;
209+ py::array_t <double > dist_arr (dist.size ());
210+ std::memcpy (dist_arr.mutable_data (), dist.data (), dist.size ()*sizeof (double ));
211+ out.append (py::make_tuple (std::move (dist_arr), pr.second ));
212+ }
213+ return out;
215214 }, py::arg (" g" ), py::arg (" src" ), py::arg (" dst" ), py::kw_only (), py::arg (" k" ), py::arg (" max_cost_factor" ) = py::none (), py::arg (" unique" ) = true , py::arg (" node_mask" ) = py::none (), py::arg (" edge_mask" ) = py::none (), py::arg (" eps" ) = 1e-10 );
216215
217216 py::class_<MinCut>(m, " MinCut" )
@@ -256,6 +255,49 @@ PYBIND11_MODULE(_netgraph_core, m) {
256255 return res;
257256 }, py::arg (" g" ), py::arg (" src" ), py::arg (" dst" ), py::kw_only (), py::arg (" flow_placement" ) = FlowPlacement::Proportional, py::arg (" shortest_path" ) = false , py::arg (" eps" ) = 1e-10 , py::arg (" with_edge_flows" ) = false , py::arg (" node_mask" ) = py::none (), py::arg (" edge_mask" ) = py::none ());
258257
258+ // Residual-aware SPF (exposes selection variants requiring capacity/flow)
259+ m.def (" spf_residual" ,
260+ [](const StrictMultiDiGraph& g, std::int32_t src, py::object dst,
261+ EdgeSelect policy, bool multipath, double eps,
262+ py::array residual, py::object node_mask, py::object edge_mask) {
263+ std::optional<std::int32_t > dst_opt;
264+ if (!dst.is_none ()) dst_opt = py::cast<std::int32_t >(dst);
265+ // residual must be 1-D float64 of length num_edges
266+ if (!(py::isinstance<py::array_t <double >>(residual))) {
267+ throw py::type_error (" residual must be a numpy float64 array" );
268+ }
269+ if (!(residual.flags () & py::array::c_style)) {
270+ throw py::type_error (" residual must be C-contiguous (np.ascontiguousarray)" );
271+ }
272+ auto rbuf = residual.request ();
273+ if (rbuf.ndim != 1 || static_cast <std::size_t >(rbuf.shape [0 ]) != static_cast <std::size_t >(g.num_edges ())) {
274+ throw py::type_error (" residual length must equal num_edges" );
275+ }
276+ std::vector<double > residual_vec (static_cast <std::size_t >(rbuf.shape [0 ]));
277+ std::memcpy (residual_vec.data (), rbuf.ptr , residual_vec.size ()*sizeof (double ));
278+ const bool * node_ptr = nullptr ; const bool * edge_ptr = nullptr ; py::array node_arr, edge_arr;
279+ if (!node_mask.is_none ()) {
280+ node_arr = py::cast<py::array>(node_mask);
281+ auto b = node_arr.request ();
282+ if (b.ndim != 1 || b.format != py::format_descriptor<bool >::format () || static_cast <std::size_t >(b.shape [0 ]) != static_cast <std::size_t >(g.num_nodes ())) {
283+ throw py::type_error (" node_mask must be 1-D bool and length=num_nodes" );
284+ }
285+ node_ptr = static_cast <const bool *>(b.ptr );
286+ }
287+ if (!edge_mask.is_none ()) {
288+ edge_arr = py::cast<py::array>(edge_mask);
289+ auto b = edge_arr.request ();
290+ if (b.ndim != 1 || b.format != py::format_descriptor<bool >::format () || static_cast <std::size_t >(b.shape [0 ]) != static_cast <std::size_t >(g.num_edges ())) {
291+ throw py::type_error (" edge_mask must be 1-D bool and length=num_edges" );
292+ }
293+ edge_ptr = static_cast <const bool *>(b.ptr );
294+ }
295+ py::gil_scoped_release rel;
296+ auto res = shortest_paths_with_residual (g, src, dst_opt, policy, multipath, eps, residual_vec, node_ptr, edge_ptr);
297+ py::gil_scoped_acquire acq;
298+ return res;
299+ }, py::arg (" g" ), py::arg (" src" ), py::arg (" dst" ) = py::none (), py::arg (" edge_select" ) = EdgeSelect::AllMinCostWithCapRemaining, py::arg (" multipath" ) = true , py::arg (" eps" ) = 1e-12 , py::arg (" residual" ), py::arg (" node_mask" ) = py::none (), py::arg (" edge_mask" ) = py::none ());
300+
259301 m.def (" max_flow" ,
260302 [](const StrictMultiDiGraph& g, std::int32_t src, std::int32_t dst,
261303 FlowPlacement placement, bool shortest_path, double eps, bool with_edge_flows,
0 commit comments