netgraph-core 0.3.2__cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _netgraph_core.cpython-312-x86_64-linux-gnu.so +0 -0
- netgraph_core/__init__.py +72 -0
- netgraph_core/_docs.py +604 -0
- netgraph_core/_version.py +5 -0
- netgraph_core/py.typed +0 -0
- netgraph_core-0.3.2.dist-info/METADATA +143 -0
- netgraph_core-0.3.2.dist-info/RECORD +9 -0
- netgraph_core-0.3.2.dist-info/WHEEL +6 -0
- netgraph_core-0.3.2.dist-info/licenses/LICENSE +148 -0
|
Binary file
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Runtime package surface for :mod:`netgraph_core`.
|
|
2
|
+
|
|
3
|
+
All public symbols are re-exported from the compiled ``_netgraph_core`` module so
|
|
4
|
+
downstream users import everything from a single place. Typing shims supplied in
|
|
5
|
+
``_docs.py`` keep editors happy without affecting runtime behavior.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# pyright: reportMissingImports=false
|
|
9
|
+
|
|
10
|
+
from _netgraph_core import (
|
|
11
|
+
Algorithms,
|
|
12
|
+
Backend,
|
|
13
|
+
EdgeSelection,
|
|
14
|
+
EdgeTieBreak,
|
|
15
|
+
FlowGraph,
|
|
16
|
+
FlowIndex,
|
|
17
|
+
FlowPlacement,
|
|
18
|
+
FlowPolicy,
|
|
19
|
+
FlowPolicyConfig,
|
|
20
|
+
FlowState,
|
|
21
|
+
FlowSummary,
|
|
22
|
+
Graph,
|
|
23
|
+
MinCut,
|
|
24
|
+
PathAlg,
|
|
25
|
+
PredDAG,
|
|
26
|
+
StrictMultiDiGraph,
|
|
27
|
+
profiling_dump,
|
|
28
|
+
profiling_enabled,
|
|
29
|
+
profiling_reset,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
from ._version import __version__
|
|
33
|
+
|
|
34
|
+
# Provide richer type information for editors/type-checkers without affecting runtime.
|
|
35
|
+
try: # pragma: no cover - typing-only import
|
|
36
|
+
from typing import TYPE_CHECKING as _TYPE_CHECKING
|
|
37
|
+
|
|
38
|
+
if _TYPE_CHECKING: # noqa: SIM108
|
|
39
|
+
from ._docs import ( # noqa: I001
|
|
40
|
+
EdgeSelection as EdgeSelection,
|
|
41
|
+
EdgeTieBreak as EdgeTieBreak,
|
|
42
|
+
FlowPlacement as FlowPlacement,
|
|
43
|
+
FlowSummary as FlowSummary,
|
|
44
|
+
MinCut as MinCut,
|
|
45
|
+
PredDAG as PredDAG,
|
|
46
|
+
)
|
|
47
|
+
except ImportError:
|
|
48
|
+
# Safe fallback if _docs.py changes; runtime bindings above remain authoritative.
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
"__version__",
|
|
53
|
+
"StrictMultiDiGraph",
|
|
54
|
+
"FlowGraph",
|
|
55
|
+
"Graph",
|
|
56
|
+
"EdgeSelection",
|
|
57
|
+
"EdgeTieBreak",
|
|
58
|
+
"PathAlg",
|
|
59
|
+
"FlowPlacement",
|
|
60
|
+
"FlowPolicy",
|
|
61
|
+
"FlowPolicyConfig",
|
|
62
|
+
"PredDAG",
|
|
63
|
+
"FlowIndex",
|
|
64
|
+
"FlowState",
|
|
65
|
+
"MinCut",
|
|
66
|
+
"FlowSummary",
|
|
67
|
+
"Backend",
|
|
68
|
+
"Algorithms",
|
|
69
|
+
"profiling_enabled",
|
|
70
|
+
"profiling_dump",
|
|
71
|
+
"profiling_reset",
|
|
72
|
+
]
|
netgraph_core/_docs.py
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
"""Typing and docstrings for netgraph_core public API.
|
|
2
|
+
|
|
3
|
+
This module is intentionally light; runtime implementations live in the
|
|
4
|
+
compiled extension. The goal is to provide type hints and help text.
|
|
5
|
+
|
|
6
|
+
TODO: replace this hand-written stub with generated `.pyi` (e.g. via
|
|
7
|
+
pybind11-stubgen) to prevent drift from the runtime bindings.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from typing import TYPE_CHECKING, Optional
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING: # only for typing; runtime comes from extension
|
|
17
|
+
import numpy as np # type: ignore[reportMissingImports]
|
|
18
|
+
# Use forward references (strings) below; avoid importing from the package
|
|
19
|
+
# to prevent circular imports during type checking.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EdgeTieBreak(Enum):
|
|
23
|
+
DETERMINISTIC = 1
|
|
24
|
+
PREFER_HIGHER_RESIDUAL = 2
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class EdgeSelection:
|
|
29
|
+
multi_edge: bool = True
|
|
30
|
+
require_capacity: bool = False
|
|
31
|
+
tie_break: EdgeTieBreak = EdgeTieBreak.DETERMINISTIC
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FlowPlacement(Enum):
|
|
35
|
+
"""How to place flow across equal-cost predecessors during augmentation.
|
|
36
|
+
|
|
37
|
+
PROPORTIONAL (WCMP-like): Distributes flow proportionally to available capacity.
|
|
38
|
+
May be used iteratively (e.g., for max-flow).
|
|
39
|
+
|
|
40
|
+
EQUAL_BALANCED (ECMP): Single-pass admission on a fixed shortest-path DAG (Dijkstra).
|
|
41
|
+
Computes one global scale so no edge is oversubscribed under equal
|
|
42
|
+
per-edge splits, places once, and stops. Re-invoking on updated
|
|
43
|
+
residuals changes the next-hop set (progressive traffic-engineering behavior).
|
|
44
|
+
ECMP = Equal-Cost Multi-Path; WCMP = Weighted-Cost Multi-Path.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
PROPORTIONAL = 1
|
|
48
|
+
EQUAL_BALANCED = 2
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class PredDAG:
|
|
52
|
+
"""Compact predecessor DAG representation.
|
|
53
|
+
|
|
54
|
+
Arrays are int32; offsets has length N+1.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
parent_offsets: np.ndarray
|
|
58
|
+
parents: np.ndarray
|
|
59
|
+
via_edges: np.ndarray
|
|
60
|
+
|
|
61
|
+
def resolve_to_paths(
|
|
62
|
+
self,
|
|
63
|
+
src: int,
|
|
64
|
+
dst: int,
|
|
65
|
+
*,
|
|
66
|
+
split_parallel_edges: bool = False,
|
|
67
|
+
max_paths: Optional[int] = None,
|
|
68
|
+
) -> list[tuple[tuple[int, tuple[int, ...]], ...]]: ...
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class PathAlg(Enum):
|
|
72
|
+
SPF = 1
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class Backend:
|
|
76
|
+
"""Backend for algorithm execution (typing stub only)."""
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def cpu() -> "Backend":
|
|
80
|
+
"""Create a CPU backend."""
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Graph:
|
|
85
|
+
"""Opaque graph handle provided by the runtime extension (typing stub only)."""
|
|
86
|
+
|
|
87
|
+
...
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class FlowGraph:
|
|
91
|
+
"""Flow graph for multi-commodity flow tracking.
|
|
92
|
+
|
|
93
|
+
Views return read-only NumPy arrays referencing internal C++ memory.
|
|
94
|
+
The FlowGraph must remain alive while views are in use.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(self, graph: "StrictMultiDiGraph") -> None: ...
|
|
98
|
+
|
|
99
|
+
def capacity_view(self) -> "np.ndarray":
|
|
100
|
+
"""Return read-only float64 view of edge capacities."""
|
|
101
|
+
...
|
|
102
|
+
|
|
103
|
+
def residual_view(self) -> "np.ndarray":
|
|
104
|
+
"""Return read-only float64 view of residual capacities."""
|
|
105
|
+
...
|
|
106
|
+
|
|
107
|
+
def edge_flow_view(self) -> "np.ndarray":
|
|
108
|
+
"""Return read-only float64 view of placed flows."""
|
|
109
|
+
...
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def graph(self) -> "StrictMultiDiGraph": ...
|
|
113
|
+
|
|
114
|
+
def place(
|
|
115
|
+
self,
|
|
116
|
+
index: "FlowIndex",
|
|
117
|
+
src: int,
|
|
118
|
+
dst: int,
|
|
119
|
+
dag: "PredDAG",
|
|
120
|
+
amount: float,
|
|
121
|
+
flow_placement: FlowPlacement = FlowPlacement.PROPORTIONAL,
|
|
122
|
+
) -> float: ...
|
|
123
|
+
|
|
124
|
+
def remove(self, index: "FlowIndex") -> None: ...
|
|
125
|
+
def remove_by_class(self, cls: int) -> None: ...
|
|
126
|
+
def reset(self) -> None: ...
|
|
127
|
+
def get_flow_edges(self, index: "FlowIndex") -> list[tuple[int, float]]: ...
|
|
128
|
+
def get_flow_path(self, index: "FlowIndex") -> list[int]: ...
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class FlowState:
|
|
132
|
+
"""Flow state tracker for a single-commodity flow problem.
|
|
133
|
+
|
|
134
|
+
Tracks per-edge residual capacity and flow placement for a graph.
|
|
135
|
+
|
|
136
|
+
**Lifetime & Memory Safety:**
|
|
137
|
+
- Views (capacity_view, residual_view, edge_flow_view) return read-only
|
|
138
|
+
NumPy arrays that directly reference internal C++ memory (zero-copy).
|
|
139
|
+
- The FlowState must remain alive while these views are in use.
|
|
140
|
+
- Arrays passed as 'residual' to __init__ or reset() are **copied** internally.
|
|
141
|
+
- Masks passed to compute_min_cut() are **copied** before releasing GIL (thread-safe).
|
|
142
|
+
|
|
143
|
+
**Array Requirements:**
|
|
144
|
+
- All arrays must be 1-D, C-contiguous, and have correct dtype (float64 for
|
|
145
|
+
residual, bool for masks).
|
|
146
|
+
- Mask lengths must match num_nodes/num_edges exactly or TypeError is raised.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __init__(
|
|
150
|
+
self, graph: "StrictMultiDiGraph", residual: "Optional[np.ndarray]" = None
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Initialize FlowState with a graph and optional residual capacities.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
graph: The network graph (kept alive automatically)
|
|
156
|
+
residual: Optional 1-D float64 array of initial residual capacities.
|
|
157
|
+
If not provided, uses graph capacities. **Array is copied.**
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
TypeError: If residual has wrong dtype, ndim, or length.
|
|
161
|
+
"""
|
|
162
|
+
...
|
|
163
|
+
|
|
164
|
+
def reset(self, residual: "Optional[np.ndarray]" = None) -> None:
|
|
165
|
+
"""Reset flow state to initial capacities.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
residual: Optional 1-D float64 array. **Array is copied.**
|
|
169
|
+
"""
|
|
170
|
+
...
|
|
171
|
+
|
|
172
|
+
def capacity_view(self) -> "np.ndarray":
|
|
173
|
+
"""Return read-only float64 view of edge capacities (zero-copy)."""
|
|
174
|
+
...
|
|
175
|
+
|
|
176
|
+
def residual_view(self) -> "np.ndarray":
|
|
177
|
+
"""Return read-only float64 view of residual capacities (zero-copy)."""
|
|
178
|
+
...
|
|
179
|
+
|
|
180
|
+
def edge_flow_view(self) -> "np.ndarray":
|
|
181
|
+
"""Return read-only float64 view of placed flows (zero-copy)."""
|
|
182
|
+
...
|
|
183
|
+
|
|
184
|
+
def place_on_dag(
|
|
185
|
+
self,
|
|
186
|
+
src: int,
|
|
187
|
+
dst: int,
|
|
188
|
+
dag: "PredDAG",
|
|
189
|
+
requested_flow: float = float("inf"),
|
|
190
|
+
flow_placement: FlowPlacement = FlowPlacement.PROPORTIONAL,
|
|
191
|
+
) -> float:
|
|
192
|
+
"""Place flow along a predecessor DAG.
|
|
193
|
+
|
|
194
|
+
EqualBalanced is **single-pass ECMP admission** on the provided DAG:
|
|
195
|
+
we compute one global scale so no edge is oversubscribed under equal per-edge
|
|
196
|
+
splits, apply it once, and return. Re-invoking on updated residuals changes
|
|
197
|
+
the next-hop set (progressive behavior).
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Amount of flow actually placed (may be less than requested).
|
|
201
|
+
"""
|
|
202
|
+
...
|
|
203
|
+
|
|
204
|
+
def place_max_flow(
|
|
205
|
+
self,
|
|
206
|
+
src: int,
|
|
207
|
+
dst: int,
|
|
208
|
+
flow_placement: FlowPlacement = FlowPlacement.PROPORTIONAL,
|
|
209
|
+
shortest_path: bool = False,
|
|
210
|
+
require_capacity: bool = True,
|
|
211
|
+
*,
|
|
212
|
+
node_mask: "Optional[np.ndarray]" = None,
|
|
213
|
+
edge_mask: "Optional[np.ndarray]" = None,
|
|
214
|
+
) -> float:
|
|
215
|
+
"""Place maximum flow from src to dst.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
require_capacity: Whether to require edges to have capacity.
|
|
219
|
+
- True (default): Routes adapt to residuals (SDN/TE, progressive fill).
|
|
220
|
+
- False: Routes based on costs only (IP/IGP, fixed routing).
|
|
221
|
+
node_mask: Optional 1-D bool array (True=allowed). Copied for thread safety.
|
|
222
|
+
edge_mask: Optional 1-D bool array (True=allowed). Copied for thread safety.
|
|
223
|
+
|
|
224
|
+
For *IP-style ECMP* max-flow, use: require_capacity=False, shortest_path=True,
|
|
225
|
+
flow_placement=EQUAL_BALANCED.
|
|
226
|
+
|
|
227
|
+
Caution: with require_capacity=True + EQUAL_BALANCED, this method iterates shortest-path +
|
|
228
|
+
placement (progressive fill), which differs from single-pass ECMP admission.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Total flow placed.
|
|
232
|
+
"""
|
|
233
|
+
...
|
|
234
|
+
|
|
235
|
+
def compute_min_cut(
|
|
236
|
+
self,
|
|
237
|
+
src: int,
|
|
238
|
+
*,
|
|
239
|
+
node_mask: "Optional[np.ndarray]" = None,
|
|
240
|
+
edge_mask: "Optional[np.ndarray]" = None,
|
|
241
|
+
) -> "MinCut":
|
|
242
|
+
"""Compute minimum cut from source based on current residual.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
src: Source node for reachability analysis
|
|
246
|
+
node_mask: Optional 1-D bool array of length num_nodes. **Copied for thread safety.**
|
|
247
|
+
edge_mask: Optional 1-D bool array of length num_edges. **Copied for thread safety.**
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
MinCut object with edges in the cut set.
|
|
251
|
+
|
|
252
|
+
Raises:
|
|
253
|
+
TypeError: If masks have wrong dtype, ndim, or length.
|
|
254
|
+
"""
|
|
255
|
+
...
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class StrictMultiDiGraph:
|
|
259
|
+
"""Opaque graph structure provided by the runtime extension (typing stub only)."""
|
|
260
|
+
|
|
261
|
+
...
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class FlowIndex:
|
|
265
|
+
src: int
|
|
266
|
+
dst: int
|
|
267
|
+
flowClass: int
|
|
268
|
+
flowId: int
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class FlowPolicyConfig:
|
|
272
|
+
"""Configuration for FlowPolicy behavior.
|
|
273
|
+
|
|
274
|
+
Key Parameters:
|
|
275
|
+
multipath: Controls whether individual flows split across multiple equal-cost paths.
|
|
276
|
+
- True (default): Hash-based ECMP - each flow uses a DAG with ALL equal-cost paths.
|
|
277
|
+
Flow volume is split across these paths according to flow_placement strategy.
|
|
278
|
+
This models router ECMP behavior where packets are hashed across next-hops.
|
|
279
|
+
- False: Tunnel-based ECMP - each flow uses a SINGLE path (one tunnel/LSP).
|
|
280
|
+
Multiple flows can share the same path. This models MPLS LSP semantics where
|
|
281
|
+
each LSP follows one specific path, and ECMP means balancing ACROSS LSPs.
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
path_alg: PathAlg
|
|
285
|
+
flow_placement: FlowPlacement
|
|
286
|
+
selection: EdgeSelection
|
|
287
|
+
require_capacity: bool
|
|
288
|
+
multipath: bool
|
|
289
|
+
min_flow_count: int
|
|
290
|
+
max_flow_count: Optional[int]
|
|
291
|
+
max_path_cost: Optional[int]
|
|
292
|
+
max_path_cost_factor: Optional[float]
|
|
293
|
+
shortest_path: bool
|
|
294
|
+
reoptimize_flows_on_each_placement: bool
|
|
295
|
+
max_no_progress_iterations: int
|
|
296
|
+
max_total_iterations: int
|
|
297
|
+
diminishing_returns_enabled: bool
|
|
298
|
+
diminishing_returns_window: int
|
|
299
|
+
diminishing_returns_epsilon_frac: float
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class FlowPolicy:
|
|
303
|
+
"""Flow policy for demand placement.
|
|
304
|
+
|
|
305
|
+
When static_paths is empty the policy may refresh the DAG per round using
|
|
306
|
+
residual-aware shortest paths. This progressively prunes saturated next-hops
|
|
307
|
+
(traffic-engineering style) and differs from one-shot ECMP admission.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
algorithms: Algorithms instance (kept alive by FlowPolicy)
|
|
311
|
+
graph: Graph handle (kept alive by FlowPolicy)
|
|
312
|
+
config: FlowPolicyConfig with all policy parameters
|
|
313
|
+
node_mask: Optional 1-D bool array (True=allowed). **Copied for thread safety.**
|
|
314
|
+
edge_mask: Optional 1-D bool array (True=allowed). **Copied for thread safety.**
|
|
315
|
+
|
|
316
|
+
Raises:
|
|
317
|
+
TypeError: If masks have wrong dtype, ndim, or length.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
def __init__(
|
|
321
|
+
self,
|
|
322
|
+
algorithms: "Algorithms",
|
|
323
|
+
graph: "Graph",
|
|
324
|
+
config: "FlowPolicyConfig",
|
|
325
|
+
*,
|
|
326
|
+
node_mask: "Optional[np.ndarray]" = None,
|
|
327
|
+
edge_mask: "Optional[np.ndarray]" = None,
|
|
328
|
+
) -> None: ...
|
|
329
|
+
|
|
330
|
+
def flow_count(self) -> int: ...
|
|
331
|
+
def placed_demand(self) -> float: ...
|
|
332
|
+
def place_demand(
|
|
333
|
+
self,
|
|
334
|
+
flow_graph: "FlowGraph",
|
|
335
|
+
src: int,
|
|
336
|
+
dst: int,
|
|
337
|
+
flowClass: int,
|
|
338
|
+
volume: float,
|
|
339
|
+
target_per_flow: Optional[float] = None,
|
|
340
|
+
min_flow: Optional[float] = None,
|
|
341
|
+
) -> tuple[float, float]: ...
|
|
342
|
+
|
|
343
|
+
def rebalance_demand(
|
|
344
|
+
self,
|
|
345
|
+
flow_graph: "FlowGraph",
|
|
346
|
+
src: int,
|
|
347
|
+
dst: int,
|
|
348
|
+
flowClass: int,
|
|
349
|
+
target: float,
|
|
350
|
+
) -> tuple[float, float]: ...
|
|
351
|
+
|
|
352
|
+
def remove_demand(self, flow_graph: "FlowGraph") -> None: ...
|
|
353
|
+
|
|
354
|
+
@property
|
|
355
|
+
def flows(self) -> dict[tuple[int, int, int, int], tuple[int, int, int, float]]: ...
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
@dataclass(frozen=True)
|
|
359
|
+
class Path:
|
|
360
|
+
nodes: np.ndarray
|
|
361
|
+
edges: np.ndarray
|
|
362
|
+
cost: float
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class MinCut:
|
|
366
|
+
edges: list[int]
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
@dataclass(frozen=True)
|
|
370
|
+
class FlowSummary:
|
|
371
|
+
total_flow: float
|
|
372
|
+
min_cut: MinCut
|
|
373
|
+
costs: "np.ndarray" # int64[K]
|
|
374
|
+
flows: "np.ndarray" # float64[K]
|
|
375
|
+
edge_flows: "np.ndarray"
|
|
376
|
+
residual_capacity: "np.ndarray"
|
|
377
|
+
reachable_nodes: "np.ndarray"
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class Algorithms:
|
|
381
|
+
"""Core graph algorithms with thread-safe bindings.
|
|
382
|
+
|
|
383
|
+
**Thread Safety & Array Handling:**
|
|
384
|
+
- All input arrays (residual, node_mask, edge_mask) are **copied** before
|
|
385
|
+
releasing the GIL, ensuring thread safety even if Python code mutates
|
|
386
|
+
the arrays concurrently.
|
|
387
|
+
- All arrays must be 1-D, C-contiguous, with correct dtype.
|
|
388
|
+
- Mask lengths must match num_nodes/num_edges exactly or TypeError is raised.
|
|
389
|
+
|
|
390
|
+
**Lifetime Management:**
|
|
391
|
+
- Algorithms instances must be kept alive while Graph handles derived from
|
|
392
|
+
them are in use (automatic via Python reference counting in normal usage).
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
def __init__(self, backend: "Backend") -> None: ...
|
|
396
|
+
|
|
397
|
+
def build_graph(self, graph: "StrictMultiDiGraph") -> "Graph":
|
|
398
|
+
"""Build a graph handle from StrictMultiDiGraph.
|
|
399
|
+
|
|
400
|
+
The graph object is kept alive automatically.
|
|
401
|
+
"""
|
|
402
|
+
...
|
|
403
|
+
|
|
404
|
+
def build_graph_from_arrays(
|
|
405
|
+
self,
|
|
406
|
+
num_nodes: int,
|
|
407
|
+
src: "np.ndarray",
|
|
408
|
+
dst: "np.ndarray",
|
|
409
|
+
capacity: "np.ndarray",
|
|
410
|
+
cost: "np.ndarray",
|
|
411
|
+
ext_edge_ids: "np.ndarray",
|
|
412
|
+
) -> "Graph":
|
|
413
|
+
"""Build graph directly from arrays (graph is owned by the handle)."""
|
|
414
|
+
...
|
|
415
|
+
|
|
416
|
+
def spf(
|
|
417
|
+
self,
|
|
418
|
+
graph: "Graph",
|
|
419
|
+
src: int,
|
|
420
|
+
dst: Optional[int] = None,
|
|
421
|
+
*,
|
|
422
|
+
selection: Optional[EdgeSelection] = None,
|
|
423
|
+
residual: Optional["np.ndarray"] = None,
|
|
424
|
+
node_mask: Optional["np.ndarray"] = None,
|
|
425
|
+
edge_mask: Optional["np.ndarray"] = None,
|
|
426
|
+
multipath: bool = True,
|
|
427
|
+
dtype: str = "float64",
|
|
428
|
+
) -> tuple["np.ndarray", "PredDAG"]:
|
|
429
|
+
"""Shortest path first algorithm.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
graph: Graph handle
|
|
433
|
+
src: Source node
|
|
434
|
+
dst: Optional destination (if None, compute from source to all)
|
|
435
|
+
selection: Edge selection policy
|
|
436
|
+
residual: Optional 1-D float64 array of residuals. **Copied for thread safety.**
|
|
437
|
+
node_mask: Optional 1-D bool mask (length num_nodes). **Copied for thread safety.**
|
|
438
|
+
True = node allowed, False = node excluded.
|
|
439
|
+
**If source node is masked (False), returns empty DAG with all distances at infinity.**
|
|
440
|
+
edge_mask: Optional 1-D bool mask (length num_edges). **Copied for thread safety.**
|
|
441
|
+
True = edge allowed, False = edge excluded.
|
|
442
|
+
multipath: Whether to track multiple equal-cost paths
|
|
443
|
+
dtype: "float64" (inf for unreachable) or "int64" (max for unreachable)
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
(distances, predecessor_dag)
|
|
447
|
+
|
|
448
|
+
Note:
|
|
449
|
+
When the source node is masked out (node_mask[src] == False), the algorithm
|
|
450
|
+
immediately returns an empty predecessor DAG with all distances set to infinity,
|
|
451
|
+
as no traversal can begin from an excluded source.
|
|
452
|
+
|
|
453
|
+
Raises:
|
|
454
|
+
TypeError: If arrays have wrong dtype, ndim, or length.
|
|
455
|
+
ValueError: If src/dst out of range.
|
|
456
|
+
"""
|
|
457
|
+
...
|
|
458
|
+
|
|
459
|
+
def ksp(
|
|
460
|
+
self,
|
|
461
|
+
graph: "Graph",
|
|
462
|
+
src: int,
|
|
463
|
+
dst: int,
|
|
464
|
+
*,
|
|
465
|
+
k: int,
|
|
466
|
+
max_cost_factor: Optional[float] = None,
|
|
467
|
+
unique: bool = True,
|
|
468
|
+
node_mask: Optional["np.ndarray"] = None,
|
|
469
|
+
edge_mask: Optional["np.ndarray"] = None,
|
|
470
|
+
dtype: str = "float64",
|
|
471
|
+
) -> list[tuple["np.ndarray", "PredDAG"]]:
|
|
472
|
+
"""K shortest paths algorithm.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
k: Number of paths to find
|
|
476
|
+
max_cost_factor: Optional maximum cost factor relative to shortest
|
|
477
|
+
node_mask: Optional 1-D bool mask. **Copied for thread safety.**
|
|
478
|
+
edge_mask: Optional 1-D bool mask. **Copied for thread safety.**
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
List of (distances, dag) tuples, up to k paths.
|
|
482
|
+
|
|
483
|
+
Note:
|
|
484
|
+
Tie-breaking is deterministic by edge ID. EdgeSelection policy is not used.
|
|
485
|
+
"""
|
|
486
|
+
...
|
|
487
|
+
|
|
488
|
+
def max_flow(
|
|
489
|
+
self,
|
|
490
|
+
graph: "Graph",
|
|
491
|
+
src: int,
|
|
492
|
+
dst: int,
|
|
493
|
+
*,
|
|
494
|
+
flow_placement: FlowPlacement = FlowPlacement.PROPORTIONAL,
|
|
495
|
+
shortest_path: bool = False,
|
|
496
|
+
require_capacity: bool = True,
|
|
497
|
+
with_edge_flows: bool = False,
|
|
498
|
+
with_reachable: bool = False,
|
|
499
|
+
with_residuals: bool = False,
|
|
500
|
+
node_mask: Optional["np.ndarray"] = None,
|
|
501
|
+
edge_mask: Optional["np.ndarray"] = None,
|
|
502
|
+
) -> tuple[float, "FlowSummary"]:
|
|
503
|
+
"""Maximum flow algorithm.
|
|
504
|
+
|
|
505
|
+
Behavior depends on require_capacity parameter:
|
|
506
|
+
|
|
507
|
+
- require_capacity=True (default): "True max-flow" - require edges to have capacity,
|
|
508
|
+
exclude saturated links (SDN/TE behavior). Uses all available paths.
|
|
509
|
+
|
|
510
|
+
- require_capacity=False: "IP ECMP" - routes based on costs only (IP/IGP behavior).
|
|
511
|
+
Routes computed once from topology, ignoring capacity. Models OSPF/IS-IS routing.
|
|
512
|
+
|
|
513
|
+
Common configurations:
|
|
514
|
+
|
|
515
|
+
1. True max-flow (SDN/TE):
|
|
516
|
+
require_capacity=True, shortest_path=False
|
|
517
|
+
|
|
518
|
+
2. IP ECMP max-flow:
|
|
519
|
+
require_capacity=False, shortest_path=True, flow_placement=EQUAL_BALANCED
|
|
520
|
+
|
|
521
|
+
3. IP WCMP max-flow:
|
|
522
|
+
require_capacity=False, shortest_path=True, flow_placement=PROPORTIONAL
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
require_capacity: Whether to require edges to have capacity.
|
|
526
|
+
shortest_path: Whether to restrict to lowest-cost tier only.
|
|
527
|
+
flow_placement: How to split flow across parallel edges (ECMP vs WCMP).
|
|
528
|
+
node_mask: Optional 1-D bool mask. **Copied for thread safety.**
|
|
529
|
+
edge_mask: Optional 1-D bool mask. **Copied for thread safety.**
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
(total_flow, flow_summary)
|
|
533
|
+
"""
|
|
534
|
+
...
|
|
535
|
+
|
|
536
|
+
def batch_max_flow(
|
|
537
|
+
self,
|
|
538
|
+
graph: "Graph",
|
|
539
|
+
pairs: "np.ndarray",
|
|
540
|
+
*,
|
|
541
|
+
node_masks: Optional[list["np.ndarray"]] = None,
|
|
542
|
+
edge_masks: Optional[list["np.ndarray"]] = None,
|
|
543
|
+
flow_placement: FlowPlacement = FlowPlacement.PROPORTIONAL,
|
|
544
|
+
shortest_path: bool = False,
|
|
545
|
+
require_capacity: bool = True,
|
|
546
|
+
with_edge_flows: bool = False,
|
|
547
|
+
with_reachable: bool = False,
|
|
548
|
+
with_residuals: bool = False,
|
|
549
|
+
) -> list[FlowSummary]:
|
|
550
|
+
"""Batch maximum flow for multiple (src,dst) pairs.
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
pairs: int32 array of shape [B, 2] with (src, dst) pairs
|
|
554
|
+
node_masks: Optional list of B bool masks. **Each copied for thread safety.**
|
|
555
|
+
edge_masks: Optional list of B bool masks. **Each copied for thread safety.**
|
|
556
|
+
require_capacity: If True, exclude saturated edges (SDN/TE). If False, route by cost only (IP/IGP).
|
|
557
|
+
|
|
558
|
+
Returns:
|
|
559
|
+
List of FlowSummary objects, one per pair.
|
|
560
|
+
"""
|
|
561
|
+
...
|
|
562
|
+
|
|
563
|
+
def sensitivity_analysis(
|
|
564
|
+
self,
|
|
565
|
+
graph: "Graph",
|
|
566
|
+
src: int,
|
|
567
|
+
dst: int,
|
|
568
|
+
*,
|
|
569
|
+
flow_placement: FlowPlacement = FlowPlacement.PROPORTIONAL,
|
|
570
|
+
shortest_path: bool = False,
|
|
571
|
+
require_capacity: bool = True,
|
|
572
|
+
node_mask: Optional["np.ndarray"] = None,
|
|
573
|
+
edge_mask: Optional["np.ndarray"] = None,
|
|
574
|
+
) -> list[tuple[int, float]]:
|
|
575
|
+
"""Sensitivity analysis to identify critical edges that constrain flow.
|
|
576
|
+
|
|
577
|
+
Computes baseline flow, then tests removing each saturated edge to
|
|
578
|
+
measure how much the total flow would be reduced.
|
|
579
|
+
|
|
580
|
+
The `shortest_path` parameter controls the routing semantics:
|
|
581
|
+
|
|
582
|
+
- shortest_path=False (default): Full max-flow analysis (SDN/TE mode).
|
|
583
|
+
Identifies edges critical for achieving maximum possible flow.
|
|
584
|
+
|
|
585
|
+
- shortest_path=True: Shortest-path-only analysis (IP/IGP mode).
|
|
586
|
+
Identifies edges critical for flow under ECMP routing. Edges on
|
|
587
|
+
unused longer paths are not reported as critical.
|
|
588
|
+
|
|
589
|
+
Args:
|
|
590
|
+
graph: Graph handle
|
|
591
|
+
src: Source node
|
|
592
|
+
dst: Destination node
|
|
593
|
+
flow_placement: Flow placement strategy
|
|
594
|
+
shortest_path: If True, use single-pass shortest-path flow (IP/IGP).
|
|
595
|
+
If False, use full iterative max-flow (SDN/TE).
|
|
596
|
+
require_capacity: If True, exclude saturated edges from routing.
|
|
597
|
+
node_mask: Optional 1-D bool mask. **Copied for thread safety.**
|
|
598
|
+
edge_mask: Optional 1-D bool mask. **Copied for thread safety.**
|
|
599
|
+
|
|
600
|
+
Returns:
|
|
601
|
+
List of (edge_id, flow_delta) tuples. Each edge_id is an edge
|
|
602
|
+
whose removal would reduce total flow by flow_delta.
|
|
603
|
+
"""
|
|
604
|
+
...
|
netgraph_core/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: netgraph-core
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: C++ implementation of graph algorithms for network flow analysis and traffic engineering with Python bindings
|
|
5
|
+
Author: Project Contributors
|
|
6
|
+
License: AGPL-3.0-or-later
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: Topic :: Scientific/Engineering
|
|
11
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
12
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: C++
|
|
19
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Project-URL: Homepage, https://github.com/networmix/NetGraph-Core
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: numpy>=1.22
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-benchmark; extra == "dev"
|
|
28
|
+
Requires-Dist: gcovr>=7; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff==0.11.13; extra == "dev"
|
|
30
|
+
Requires-Dist: pyright==1.1.401; extra == "dev"
|
|
31
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
32
|
+
Requires-Dist: build; extra == "dev"
|
|
33
|
+
Requires-Dist: twine; extra == "dev"
|
|
34
|
+
Requires-Dist: cmake>=3.23; extra == "dev"
|
|
35
|
+
Requires-Dist: ninja; extra == "dev"
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# NetGraph-Core
|
|
39
|
+
|
|
40
|
+
C++ graph engine for network flow analysis, traffic engineering simulation, and capacity planning.
|
|
41
|
+
|
|
42
|
+
## Overview
|
|
43
|
+
|
|
44
|
+
NetGraph-Core provides a specialized graph implementation for networking problems. Key design priorities:
|
|
45
|
+
|
|
46
|
+
- **Determinism**: Guaranteed reproducible edge ordering by (cost, src, dst).
|
|
47
|
+
- **Flow Modeling**: Native support for multi-commodity flow state, residual tracking, and ECMP/WCMP placement.
|
|
48
|
+
- **Performance**: Immutable CSR (Compressed Sparse Row) adjacency and zero-copy NumPy views.
|
|
49
|
+
|
|
50
|
+
## Core Features
|
|
51
|
+
|
|
52
|
+
### 1. Graph Representations
|
|
53
|
+
|
|
54
|
+
- **`StrictMultiDiGraph`**: Immutable directed multigraph using CSR adjacency. Supports parallel edges (multi-graph), essential for network topologies.
|
|
55
|
+
- **`FlowGraph`**: Topology overlay managing mutable flow state, per-flow edge allocations, and residual capacities.
|
|
56
|
+
|
|
57
|
+
### 2. Network Algorithms
|
|
58
|
+
|
|
59
|
+
- **Shortest Paths (SPF)**:
|
|
60
|
+
- Modified Dijkstra returns a **Predecessor DAG** to capture all equal-cost paths.
|
|
61
|
+
- Supports **ECMP** (Equal-Cost Multi-Path) routing.
|
|
62
|
+
- Features **node/edge masking** and **residual-aware tie-breaking**.
|
|
63
|
+
|
|
64
|
+
- **K-Shortest Paths (KSP)**:
|
|
65
|
+
- Yen's algorithm returning DAG-wrapped paths.
|
|
66
|
+
- Configurable constraints on cost factors (e.g., paths within 1.5x of optimal).
|
|
67
|
+
|
|
68
|
+
- **Max-Flow**:
|
|
69
|
+
- **Algorithm**: Iterative augmentation using Successive Shortest Path on residual graphs, pushing flow across full ECMP/WCMP DAGs at each step.
|
|
70
|
+
- **Traffic Engineering (TE) Mode**: Routing adapts to residual capacity (progressive fill).
|
|
71
|
+
- **IP Routing Mode**: Cost-only routing (ECMP/WCMP) ignoring capacity constraints.
|
|
72
|
+
|
|
73
|
+
- **Analysis**:
|
|
74
|
+
- **Sensitivity Analysis**: Identifies bottleneck edges where capacity relaxation increases total flow. Supports `shortest_path` mode to analyze only edges used under ECMP routing (IP/IGP networks) vs. full max-flow (SDN/TE networks).
|
|
75
|
+
- **Min-Cut**: Computes minimum cuts on residual graphs.
|
|
76
|
+
|
|
77
|
+
### 3. Flow Policy Engine
|
|
78
|
+
|
|
79
|
+
Unified configuration object (`FlowPolicy`) that models diverse routing behaviors:
|
|
80
|
+
|
|
81
|
+
- **Modeling**: Unified configuration for **IP Routing** (static costs) and **Traffic Engineering** (dynamic residuals).
|
|
82
|
+
- **Placement Strategies**:
|
|
83
|
+
- `EqualBalanced`: **ECMP** (equal splitting) - equal distribution across next-hops and parallel edges.
|
|
84
|
+
- `Proportional`: **WCMP** (weighted splitting) - distribution proportional to residual capacity.
|
|
85
|
+
- **Lifecycle Management**: Handles demand placement, re-optimization of existing flows, and constraints (path cost, stretch factor, flow counts).
|
|
86
|
+
|
|
87
|
+
### 4. Python Integration
|
|
88
|
+
|
|
89
|
+
- **Zero-Copy**: Exposes C++ internal buffers to Python as read-only NumPy arrays (float64/int64).
|
|
90
|
+
- **Concurrency**: Releases the Python GIL during graph algorithms to enable threading.
|
|
91
|
+
|
|
92
|
+
## Installation
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pip install netgraph-core
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Or from source:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install -e .
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Build Optimizations
|
|
105
|
+
|
|
106
|
+
Default builds include LTO and loop unrolling. For local development:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
make install-native # CPU-specific optimizations (not portable)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Repository Structure
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
src/ # C++ implementation
|
|
116
|
+
include/netgraph/core/ # Public C++ headers
|
|
117
|
+
bindings/python/ # pybind11 bindings
|
|
118
|
+
python/netgraph_core/ # Python package
|
|
119
|
+
tests/cpp/ # C++ tests (googletest)
|
|
120
|
+
tests/py/ # Python tests (pytest)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
make dev # Setup: venv, dependencies, pre-commit hooks
|
|
127
|
+
make check # Run all tests and linting (auto-fix formatting)
|
|
128
|
+
make check-ci # Strict checks without auto-fix (for CI)
|
|
129
|
+
make test # Python tests with coverage
|
|
130
|
+
make cpp-test # C++ tests only
|
|
131
|
+
make cov # Combined coverage report (C++ + Python)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Requirements
|
|
135
|
+
|
|
136
|
+
- **C++:** C++20 compiler (GCC 10+, Clang 12+, MSVC 2019+)
|
|
137
|
+
- **Python:** 3.11+
|
|
138
|
+
- **Build:** CMake 3.15+, scikit-build-core
|
|
139
|
+
- **Dependencies:** pybind11, NumPy
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
AGPL-3.0-or-later
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
_netgraph_core.cpython-312-x86_64-linux-gnu.so,sha256=imS3CpY193RGEPjhyuPKrEwB3Gs74A-0lel1tYcTZhg,869120
|
|
2
|
+
netgraph_core-0.3.2.dist-info/RECORD,,
|
|
3
|
+
netgraph_core-0.3.2.dist-info/WHEEL,sha256=mHU8IgtXlS9dt0QiyHytIewnnNX1NrwZMGogjhxiHgs,156
|
|
4
|
+
netgraph_core-0.3.2.dist-info/METADATA,sha256=Ic6cJqGuzefLnGuAYnbpEdsvBO4A2MwcD7Sm97kxPpg,5536
|
|
5
|
+
netgraph_core-0.3.2.dist-info/licenses/LICENSE,sha256=lKbJTfEk6X57dZfORMJJrYPQGNQk8ypxQnlh14A48lc,7070
|
|
6
|
+
netgraph_core/__init__.py,sha256=Crlj39aIZV0STiL3_w6S2FH4x13Tj906gb2x6Zjg0jY,1740
|
|
7
|
+
netgraph_core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
netgraph_core/_docs.py,sha256=qOflLG0zDQ2un0zxQ9q6CdqXbaDSs8CDdWFuN91cgs0,20367
|
|
9
|
+
netgraph_core/_version.py,sha256=uHeg5yAzQiHBeFhKJ-6zwAQdxbT4eJQEsRLbWYMwd8g,88
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
GNU AFFERO GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 3, 19 November 2007
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
5
|
+
Everyone is permitted to copy and distribute verbatim copies
|
|
6
|
+
of this license document, but changing it is not allowed.
|
|
7
|
+
|
|
8
|
+
Preamble
|
|
9
|
+
|
|
10
|
+
The GNU Affero General Public License is a free, copyleft license for
|
|
11
|
+
software and other kinds of works, specifically designed to ensure
|
|
12
|
+
cooperation with the community in the case of network server software.
|
|
13
|
+
|
|
14
|
+
The licenses for most software and other practical works are designed
|
|
15
|
+
to take away your freedom to share and change the works. By contrast,
|
|
16
|
+
our General Public Licenses are intended to guarantee your freedom to
|
|
17
|
+
share and change all versions of a program--to make sure it remains free
|
|
18
|
+
software for all its users.
|
|
19
|
+
|
|
20
|
+
When we speak of free software, we are referring to freedom, not
|
|
21
|
+
price. Our General Public Licenses are designed to make sure that you
|
|
22
|
+
have the freedom to distribute copies of free software (and charge for
|
|
23
|
+
them if you wish), that you receive source code or can get it if you
|
|
24
|
+
want it, that you can change the software or use pieces of it in new
|
|
25
|
+
free programs, and that you know you can do these things.
|
|
26
|
+
|
|
27
|
+
Developers that use our General Public Licenses protect your rights
|
|
28
|
+
with two steps: (1) assert copyright on the software, and (2) offer
|
|
29
|
+
you this License which gives you legal permission to copy, distribute
|
|
30
|
+
and/or modify the software.
|
|
31
|
+
|
|
32
|
+
A secondary benefit of defending all users' freedom is that
|
|
33
|
+
improvements made in alternate versions of the program, if they
|
|
34
|
+
receive widespread use, become available for other developers to
|
|
35
|
+
incorporate. Many developers of free software are heartened and
|
|
36
|
+
encouraged by the resulting cooperation. However, in the case of
|
|
37
|
+
software used on network servers, this result may fail to come about.
|
|
38
|
+
The GNU General Public License permits making a modified version and
|
|
39
|
+
letting the public access it on a server without ever releasing its
|
|
40
|
+
source code to the public.
|
|
41
|
+
|
|
42
|
+
The GNU Affero General Public License is designed specifically to
|
|
43
|
+
ensure that, in such cases, the modified source code becomes available
|
|
44
|
+
to the community. It requires the operator of a network server to
|
|
45
|
+
provide the source code of the modified version running there to the
|
|
46
|
+
users of that server. Therefore, public use of a modified version, on
|
|
47
|
+
a publicly accessible server, gives the public access to the source
|
|
48
|
+
code of the modified version.
|
|
49
|
+
|
|
50
|
+
An older license, called the Affero General Public License and
|
|
51
|
+
published by Affero, was designed to accomplish similar goals. This is
|
|
52
|
+
a different license, not a version of the Affero GPL, but Affero has
|
|
53
|
+
released a new version of the Affero GPL which permits relicensing under
|
|
54
|
+
this license.
|
|
55
|
+
|
|
56
|
+
The precise terms and conditions for copying, distribution and
|
|
57
|
+
modification follow.
|
|
58
|
+
|
|
59
|
+
TERMS AND CONDITIONS
|
|
60
|
+
|
|
61
|
+
0. Definitions.
|
|
62
|
+
|
|
63
|
+
"This License" refers to version 3 of the GNU Affero General Public License.
|
|
64
|
+
|
|
65
|
+
"Copyright" also means copyright-like laws that apply to other kinds of
|
|
66
|
+
works, such as semiconductor masks.
|
|
67
|
+
|
|
68
|
+
"The Program" refers to any copyrightable work licensed under this
|
|
69
|
+
License. Each licensee is addressed as "you". "Licensees" and
|
|
70
|
+
"recipients" may be individuals or organizations.
|
|
71
|
+
|
|
72
|
+
To "modify" a work means to copy from or adapt all or part of the work
|
|
73
|
+
in a fashion requiring copyright permission, other than the making of an
|
|
74
|
+
exact copy. The resulting work is called a "modified version" of the
|
|
75
|
+
earlier work or a work "based on" the earlier work.
|
|
76
|
+
|
|
77
|
+
A "covered work" means either the unmodified Program or a work based
|
|
78
|
+
on the Program.
|
|
79
|
+
|
|
80
|
+
To "propagate" a work means to do anything with it that, without
|
|
81
|
+
permission, would make you directly or secondarily liable for
|
|
82
|
+
infringement under applicable copyright law, except executing it on a
|
|
83
|
+
computer or modifying a private copy. Propagation includes copying,
|
|
84
|
+
distribution (with or without modification), making available to the
|
|
85
|
+
public, and in some countries other activities as well.
|
|
86
|
+
|
|
87
|
+
To "convey" a work means any kind of propagation that enables other
|
|
88
|
+
parties to make or receive copies. Mere interaction with a user through
|
|
89
|
+
a computer network, with no transfer of a copy, is not conveying.
|
|
90
|
+
|
|
91
|
+
An interactive user interface displays "Appropriate Legal Notices"
|
|
92
|
+
to the extent that it includes a convenient and prominently visible
|
|
93
|
+
feature that (1) displays an appropriate copyright notice, and (2)
|
|
94
|
+
tells the user that there is no warranty for the work (except to the
|
|
95
|
+
extent that warranties are provided), that licensees may convey the
|
|
96
|
+
work under this License, and how to view a copy of this License. If
|
|
97
|
+
the interface presents a list of user commands or options, such as a
|
|
98
|
+
menu, a prominent item in the list meets this criterion.
|
|
99
|
+
|
|
100
|
+
1. Source Code.
|
|
101
|
+
|
|
102
|
+
The "source code" for a work means the preferred form of the work
|
|
103
|
+
for making modifications to it. "Object code" means any non-source
|
|
104
|
+
form of a work.
|
|
105
|
+
|
|
106
|
+
... (AGPLv3 full text continues; include the entire standard text without modification) ...
|
|
107
|
+
|
|
108
|
+
How to Apply These Terms to Your New Programs
|
|
109
|
+
|
|
110
|
+
If you develop a new program, and you want it to be of the greatest
|
|
111
|
+
possible use to the public, the best way to achieve this is to make it
|
|
112
|
+
free software which everyone can redistribute and change under these terms.
|
|
113
|
+
|
|
114
|
+
To do so, attach the following notices to the program. It is safest
|
|
115
|
+
to attach them to the start of each source file to most effectively
|
|
116
|
+
state the exclusion of warranty; and each file should have at least
|
|
117
|
+
the "copyright" line and a pointer to where the full notice is found.
|
|
118
|
+
|
|
119
|
+
<one line to give the program's name and a brief idea of what it does.>
|
|
120
|
+
Copyright (C) <year> <name of author>
|
|
121
|
+
|
|
122
|
+
This program is free software: you can redistribute it and/or modify
|
|
123
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
124
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
125
|
+
(at your option) any later version.
|
|
126
|
+
|
|
127
|
+
This program is distributed in the hope that it will be useful,
|
|
128
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
129
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
130
|
+
GNU Affero General Public License for more details.
|
|
131
|
+
|
|
132
|
+
You should have received a copy of the GNU Affero General Public License
|
|
133
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
134
|
+
|
|
135
|
+
Also add information on how to contact you by electronic and paper mail.
|
|
136
|
+
|
|
137
|
+
If your software can interact with users remotely through a computer
|
|
138
|
+
network, you should also make sure that it provides a way for users to
|
|
139
|
+
get its source. For example, if your program is a web application, its
|
|
140
|
+
interface could display a "Source" link that leads users to an archive
|
|
141
|
+
of the code. There are many ways you could offer source, and different
|
|
142
|
+
solutions will be better for different programs; see section 13 for the
|
|
143
|
+
specific requirements.
|
|
144
|
+
|
|
145
|
+
You should also get your employer (if you work as a programmer) or school,
|
|
146
|
+
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
|
147
|
+
For more information on this, and how to apply and follow the GNU AGPL, see
|
|
148
|
+
<https://www.gnu.org/licenses/>.
|