Skip to content

Commit cb4b5eb

Browse files
committed
streamlined implementation
1 parent fb87551 commit cb4b5eb

35 files changed

Lines changed: 1113 additions & 901 deletions

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
cmake_minimum_required(VERSION 3.23)
22
project(netgraph_core LANGUAGES CXX)
3+
# Silence pybind11 FindPython compatibility warning by selecting the modern mode
4+
set(PYBIND11_FINDPYTHON NEW CACHE STRING "Use modern FindPython in pybind11")
35
# Optional C++ tests
46
option(NETGRAPH_CORE_BUILD_TESTS "Build C++ unit tests" OFF)
57
if(NETGRAPH_CORE_BUILD_TESTS)
@@ -57,6 +59,7 @@ if(NOT pybind11_FOUND)
5759
FetchContent_MakeAvailable(pybind11)
5860
endif()
5961

62+
6063
add_library(netgraph_core STATIC
6164
src/strict_multidigraph.cpp
6265
src/shortest_paths.cpp

Makefile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
VENV_BIN := $(PWD)/venv/bin
88
# Use dynamic (recursive) assignment so a newly created venv is picked up
9-
PYTHON = $(if $(wildcard $(VENV_BIN)/python),$(VENV_BIN)/python,python3)
9+
PY_FIND := $(shell command -v python3.13 2>/dev/null || command -v python3 2>/dev/null)
10+
PYTHON = $(if $(wildcard $(VENV_BIN)/python),$(VENV_BIN)/python,$(PY_FIND))
1011
PIP = $(PYTHON) -m pip
1112
PYTEST = $(PYTHON) -m pytest
1213
RUFF = $(PYTHON) -m ruff
@@ -46,7 +47,7 @@ dev:
4647
@echo "🚀 Setting up development environment..."
4748
@if [ ! -x "$(VENV_BIN)/python" ]; then \
4849
echo "🐍 Creating virtual environment in ./venv ..."; \
49-
python3 -m venv venv; \
50+
$(PY_FIND) -m venv venv; \
5051
$(VENV_BIN)/python -m pip install -U pip wheel; \
5152
fi
5253
@echo "📦 Installing dev dependencies..."
@@ -57,7 +58,7 @@ dev:
5758

5859
venv:
5960
@echo "🐍 Creating virtual environment in ./venv ..."
60-
@python3 -m venv venv
61+
@$(PY_FIND) -m venv venv
6162
@$(VENV_BIN)/python -m pip install -U pip wheel
6263
@echo "✅ venv ready. Activate with: source venv/bin/activate"
6364

bindings/python/module.cpp

Lines changed: 290 additions & 232 deletions
Large diffs are not rendered by default.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* Algorithms façade that forwards to a selected Backend implementation. */
2+
#pragma once
3+
4+
#include <memory>
5+
6+
#include "netgraph/core/backend.hpp"
7+
#include "netgraph/core/options.hpp"
8+
9+
namespace netgraph::core {
10+
11+
class Algorithms {
12+
public:
13+
explicit Algorithms(BackendPtr backend) : backend_(std::move(backend)) {}
14+
15+
[[nodiscard]] GraphHandle build_graph(const StrictMultiDiGraph& g) const {
16+
return backend_->build_graph(g);
17+
}
18+
19+
[[nodiscard]] GraphHandle build_graph(std::shared_ptr<const StrictMultiDiGraph> g) const {
20+
return backend_->build_graph(std::move(g));
21+
}
22+
23+
[[nodiscard]] std::pair<std::vector<Cost>, PredDAG>
24+
spf(const GraphHandle& gh, NodeId src, const SpfOptions& opts) const {
25+
return backend_->spf(gh, src, opts);
26+
}
27+
28+
[[nodiscard]] std::vector<std::pair<std::vector<Cost>, PredDAG>>
29+
ksp(const GraphHandle& gh, NodeId src, NodeId dst, const KspOptions& opts) const {
30+
return backend_->ksp(gh, src, dst, opts);
31+
}
32+
33+
[[nodiscard]] std::pair<Flow, FlowSummary>
34+
max_flow(const GraphHandle& gh, NodeId src, NodeId dst, const MaxFlowOptions& opts) const {
35+
return backend_->max_flow(gh, src, dst, opts);
36+
}
37+
38+
[[nodiscard]] std::vector<FlowSummary>
39+
batch_max_flow(const GraphHandle& gh,
40+
const std::vector<std::pair<NodeId,NodeId>>& pairs,
41+
const MaxFlowOptions& opts,
42+
const std::vector<std::span<const bool>>& node_masks = {},
43+
const std::vector<std::span<const bool>>& edge_masks = {}) const {
44+
return backend_->batch_max_flow(gh, pairs, opts, node_masks, edge_masks);
45+
}
46+
47+
private:
48+
BackendPtr backend_;
49+
};
50+
51+
} // namespace netgraph::core

include/netgraph/core/backend.hpp

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
/*
2-
ExecutionBackend interface — abstracts SP/MaxFlow/KSP implementations.
2+
Backend interface — abstracts SPF/MaxFlow/KSP implementations.
33
44
The default CPU backend delegates to in-process algorithm implementations.
5-
Interfaces mirror the public helpers in headers (e.g., see max_flow.hpp)
6-
including optional toggles for collecting additional outputs.
5+
All execution flows through this interface via an explicit Algorithms façade.
76
*/
87
#pragma once
98

@@ -13,39 +12,50 @@
1312
#include <vector>
1413
#include <span>
1514

16-
#include "netgraph/core/max_flow.hpp"
17-
#include "netgraph/core/shortest_paths.hpp"
1815
#include "netgraph/core/strict_multidigraph.hpp"
16+
#include "netgraph/core/shortest_paths.hpp"
17+
#include "netgraph/core/max_flow.hpp"
18+
#include "netgraph/core/options.hpp"
1919

2020
namespace netgraph::core {
2121

22-
class ExecutionBackend {
22+
// Opaque handle to a backend-owned graph. Uses shared ownership to provide
23+
// safe lifetime management across API boundaries.
24+
struct GraphHandle {
25+
std::shared_ptr<const StrictMultiDiGraph> graph {};
26+
};
27+
28+
class Backend {
2329
public:
24-
virtual ~ExecutionBackend() noexcept = default;
25-
[[nodiscard]] virtual std::pair<std::vector<Cost>, PredDAG> shortest_paths(
26-
const StrictMultiDiGraph& g, NodeId src, std::optional<NodeId> dst,
27-
const EdgeSelection& selection,
28-
std::span<const Cap> residual = {},
29-
const bool* node_mask = nullptr,
30-
const bool* edge_mask = nullptr) = 0;
31-
32-
[[nodiscard]] virtual std::pair<Flow, FlowSummary> calc_max_flow(
33-
const StrictMultiDiGraph& g, NodeId src, NodeId dst,
34-
FlowPlacement placement, bool shortest_path,
35-
bool with_edge_flows,
36-
bool with_reachable,
37-
bool with_residuals,
38-
const bool* node_mask = nullptr,
39-
const bool* edge_mask = nullptr) = 0;
40-
41-
[[nodiscard]] virtual std::vector<std::pair<std::vector<Cost>, PredDAG>> k_shortest_paths(
42-
const StrictMultiDiGraph& g, NodeId src, NodeId dst,
43-
int k, std::optional<double> max_cost_factor,
44-
bool unique,
45-
const bool* node_mask = nullptr,
46-
const bool* edge_mask = nullptr) = 0;
30+
virtual ~Backend() noexcept = default;
31+
32+
// Prepare a backend-specific graph handle from an existing graph reference.
33+
// CPU backend creates a non-owning shared_ptr with a no-op deleter.
34+
[[nodiscard]] virtual GraphHandle build_graph(const StrictMultiDiGraph& g) = 0;
35+
36+
// Prepare a backend-specific graph handle that takes shared ownership of the
37+
// provided graph instance.
38+
[[nodiscard]] virtual GraphHandle build_graph(std::shared_ptr<const StrictMultiDiGraph> g) = 0;
39+
40+
[[nodiscard]] virtual std::pair<std::vector<Cost>, PredDAG> spf(
41+
const GraphHandle& gh, NodeId src, const SpfOptions& opts) = 0;
42+
43+
[[nodiscard]] virtual std::pair<Flow, FlowSummary> max_flow(
44+
const GraphHandle& gh, NodeId src, NodeId dst, const MaxFlowOptions& opts) = 0;
45+
46+
[[nodiscard]] virtual std::vector<std::pair<std::vector<Cost>, PredDAG>> ksp(
47+
const GraphHandle& gh, NodeId src, NodeId dst, const KspOptions& opts) = 0;
48+
49+
[[nodiscard]] virtual std::vector<FlowSummary> batch_max_flow(
50+
const GraphHandle& gh,
51+
const std::vector<std::pair<NodeId,NodeId>>& pairs,
52+
const MaxFlowOptions& opts,
53+
const std::vector<std::span<const bool>>& node_masks = {},
54+
const std::vector<std::span<const bool>>& edge_masks = {}) = 0;
4755
};
4856

49-
[[nodiscard]] std::unique_ptr<ExecutionBackend> make_cpu_backend();
57+
using BackendPtr = std::shared_ptr<Backend>;
58+
59+
[[nodiscard]] BackendPtr make_cpu_backend();
5060

5161
} // namespace netgraph::core

include/netgraph/core/flow_policy.hpp

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#pragma once
66

77
#include <cstdint>
8+
#include <limits>
89
#include <optional>
910
#include <unordered_map>
1011
#include <utility>
@@ -13,18 +14,64 @@
1314

1415
#include "netgraph/core/flow.hpp"
1516
#include "netgraph/core/flow_graph.hpp"
16-
#include "netgraph/core/shortest_paths.hpp"
17+
#include "netgraph/core/algorithms.hpp"
18+
#include "netgraph/core/options.hpp"
1719
#include "netgraph/core/types.hpp"
1820

1921
namespace netgraph::core {
2022

2123
enum class PathAlg : std::int32_t { SPF = 1 };
2224

25+
// Execution context bundles algorithms and graph handle for clear dependency injection
26+
struct ExecutionContext {
27+
Algorithms* algorithms;
28+
GraphHandle graph;
29+
30+
// Constructor with validation
31+
ExecutionContext(Algorithms& algs, const GraphHandle& gh) noexcept
32+
: algorithms(&algs), graph(gh) {}
33+
};
34+
35+
// Configuration for FlowPolicy behavior. Mirrors the long-form constructor
36+
// parameters in a grouped, maintainable struct.
37+
struct FlowPolicyConfig {
38+
PathAlg path_alg { PathAlg::SPF };
39+
FlowPlacement flow_placement { FlowPlacement::Proportional };
40+
EdgeSelection selection { EdgeSelection{} };
41+
int min_flow_count { 1 };
42+
std::optional<int> max_flow_count { std::nullopt };
43+
std::optional<Cost> max_path_cost { std::nullopt };
44+
std::optional<double> max_path_cost_factor { std::nullopt };
45+
bool shortest_path { false };
46+
bool reoptimize_flows_on_each_placement { false };
47+
int max_no_progress_iterations { 100 };
48+
int max_total_iterations { 10000 };
49+
bool diminishing_returns_enabled { true };
50+
int diminishing_returns_window { 8 };
51+
double diminishing_returns_epsilon_frac { 1e-3 };
52+
};
53+
2354
// FlowPolicy orchestrates flow creation, placement, reopt, and removal for a
2455
// single demand (src,dst,flowClass) on a shared FlowGraph.
2556
class FlowPolicy {
2657
public:
27-
FlowPolicy(PathAlg path_alg,
58+
// New config-based constructor
59+
FlowPolicy(const ExecutionContext& ctx, const FlowPolicyConfig& cfg)
60+
: ctx_(ctx),
61+
path_alg_(cfg.path_alg), flow_placement_(cfg.flow_placement), selection_(cfg.selection),
62+
shortest_path_(cfg.shortest_path),
63+
min_flow_count_(cfg.min_flow_count), max_flow_count_(cfg.max_flow_count), max_path_cost_(cfg.max_path_cost),
64+
max_path_cost_factor_(cfg.max_path_cost_factor), reoptimize_flows_on_each_placement_(cfg.reoptimize_flows_on_each_placement),
65+
max_no_progress_iterations_(cfg.max_no_progress_iterations), max_total_iterations_(cfg.max_total_iterations),
66+
diminishing_returns_enabled_(cfg.diminishing_returns_enabled), diminishing_returns_window_(cfg.diminishing_returns_window),
67+
diminishing_returns_epsilon_frac_(cfg.diminishing_returns_epsilon_frac) {
68+
if (flow_placement_ == FlowPlacement::EqualBalanced && !max_flow_count_.has_value()) {
69+
throw std::invalid_argument("max_flow_count must be set for EQUAL_BALANCED placement.");
70+
}
71+
}
72+
73+
FlowPolicy(const ExecutionContext& ctx,
74+
PathAlg path_alg,
2875
FlowPlacement flow_placement,
2976
EdgeSelection selection,
3077
int min_flow_count = 1,
@@ -38,7 +85,8 @@ class FlowPolicy {
3885
bool diminishing_returns_enabled = true,
3986
int diminishing_returns_window = 8,
4087
double diminishing_returns_epsilon_frac = 1e-3)
41-
: path_alg_(path_alg), flow_placement_(flow_placement), selection_(selection),
88+
: ctx_(ctx),
89+
path_alg_(path_alg), flow_placement_(flow_placement), selection_(selection),
4290
shortest_path_(shortest_path),
4391
min_flow_count_(min_flow_count), max_flow_count_(max_flow_count), max_path_cost_(max_path_cost),
4492
max_path_cost_factor_(max_path_cost_factor), reoptimize_flows_on_each_placement_(reoptimize_flows_on_each_placement),
@@ -87,6 +135,7 @@ class FlowPolicy {
87135
[[nodiscard]] FlowRecord* reoptimize_flow(FlowGraph& fg, const FlowIndex& idx, double headroom);
88136

89137
// Config
138+
ExecutionContext ctx_;
90139
PathAlg path_alg_ { PathAlg::SPF };
91140
FlowPlacement flow_placement_ { FlowPlacement::Proportional };
92141
EdgeSelection selection_ { EdgeSelection{} };
@@ -104,14 +153,9 @@ class FlowPolicy {
104153

105154
// State
106155
std::unordered_map<FlowIndex, FlowRecord, FlowIndexHash> flows_;
107-
Cost best_path_cost_ { 0 };
156+
Cost best_path_cost_ { std::numeric_limits<Cost>::max() };
108157
std::int64_t next_flow_id_ { 0 };
109158

110-
// When selection_.multipath == false, freeze exactly one edge per neighbor
111-
// pair (u,v) across all flows: subsequent path searches may only reuse the
112-
// same chosen EdgeId for that (u,v). Key is (static_cast<uint64_t>(u)<<32)|v.
113-
std::unordered_map<std::uint64_t, EdgeId> locked_uv_edge_;
114-
115159
// Static paths (optional)
116160
std::vector<std::tuple<NodeId, NodeId, PredDAG, Cost>> static_paths_;
117161
};

include/netgraph/core/options.hpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* Option structs for algorithm invocations to keep interfaces concise. */
2+
#pragma once
3+
4+
#include <optional>
5+
#include <span>
6+
7+
#include "netgraph/core/types.hpp"
8+
9+
namespace netgraph::core {
10+
11+
struct SpfOptions {
12+
bool multipath { true };
13+
EdgeSelection selection {};
14+
std::optional<NodeId> dst {};
15+
std::span<const Cap> residual {};
16+
std::span<const bool> node_mask {};
17+
std::span<const bool> edge_mask {};
18+
};
19+
20+
struct KspOptions {
21+
int k { 1 };
22+
std::optional<double> max_cost_factor {};
23+
bool unique { true };
24+
std::span<const bool> node_mask {};
25+
std::span<const bool> edge_mask {};
26+
};
27+
28+
struct MaxFlowOptions {
29+
FlowPlacement placement { FlowPlacement::Proportional };
30+
bool shortest_path { false };
31+
bool with_edge_flows { false };
32+
bool with_reachable { false };
33+
bool with_residuals { false };
34+
std::span<const bool> node_mask {};
35+
std::span<const bool> edge_mask {};
36+
};
37+
38+
} // namespace netgraph::core

include/netgraph/core/shortest_paths.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ struct PredDAG {
2929
[[nodiscard]] std::pair<std::vector<Cost>, PredDAG>
3030
shortest_paths(const StrictMultiDiGraph& g, NodeId src,
3131
std::optional<NodeId> dst,
32+
bool multipath,
3233
const EdgeSelection& selection,
3334
std::span<const Cap> residual = {},
3435
const bool* node_mask = nullptr,

include/netgraph/core/types.hpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#pragma once
33

44
#include <cstdint>
5+
#include <functional>
56

67
namespace netgraph::core {
78

@@ -26,11 +27,15 @@ struct FlowIndex {
2627

2728
struct FlowIndexHash {
2829
std::size_t operator()(const FlowIndex& k) const noexcept {
29-
// simple mix
30-
std::size_t h = static_cast<std::size_t>(static_cast<std::uint64_t>(k.src) * 1469598103934665603ULL);
31-
h ^= static_cast<std::size_t>(static_cast<std::uint32_t>(k.dst)) + 0x9e3779b97f4a7c15ULL + (h<<6) + (h>>2);
32-
h ^= static_cast<std::size_t>(static_cast<std::uint32_t>(k.flowClass)) + 0x9e3779b97f4a7c15ULL + (h<<6) + (h>>2);
33-
h ^= static_cast<std::size_t>(static_cast<std::uint64_t>(k.flowId)) + 0x9e3779b97f4a7c15ULL + (h<<6) + (h>>2);
30+
std::size_t h = 0;
31+
auto combine = [&h](std::size_t v) {
32+
// Standard hash combine (boost-like)
33+
h ^= v + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
34+
};
35+
combine(std::hash<NodeId>{}(k.src));
36+
combine(std::hash<NodeId>{}(k.dst));
37+
combine(std::hash<std::int32_t>{}(k.flowClass));
38+
combine(std::hash<std::int64_t>{}(k.flowId));
3439
return h;
3540
}
3641
};
@@ -43,7 +48,9 @@ enum class FlowPlacement {
4348
enum class EdgeTieBreak { Deterministic = 1, PreferHigherResidual = 2 };
4449

4550
struct EdgeSelection {
46-
bool multipath { true };
51+
// When true, keep all equal-cost parallel edges per (u,v) adjacency.
52+
// When false, pick a single edge per (u,v) according to tie_break.
53+
bool multi_edge { true };
4754
bool require_capacity { false };
4855
EdgeTieBreak tie_break { EdgeTieBreak::Deterministic };
4956
};

0 commit comments

Comments
 (0)