Skip to content

NetGraph-Core 0.12.0: the rebuilt engine - #12

Merged
networmix merged 39 commits into
mainfrom
streamline
Oct 27, 2026
Merged

networmix merged 39 commits into
mainfrom
streamline

Conversation

@networmix

@networmix networmix commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

Draft. 0.12.0 is tagged and published once every CI check below passes. NetGraph 0.25.0 is built on this release.

What changes

0.12.0 replaces the 0.11 API; it is a breaking release. README.md gives the ideas and a Quick Start, and CHANGELOG.md lists every change.

  • Exact amounts. Capacities and traffic are whole numbers of units of a resolution q, so no result depends on floating-point tolerance.
  • One way to route a demand. A FlowPolicy places a demand on a FlowLedger with one routing model:
    • ShortestPaths: ECMP or WCMP;
    • DistributedTE: RSVP-TE-style tunnels set up by each source router;
    • CentralizedTE: an ideal controller, optionally with the same number of equal tunnels for every source router;
    • Pinned: replays a saved program.
  • Evaluators.
    • evaluate_demand: one demand at full capacity; with CentralizedTE, the exact maximum flow, its cost curve and its minimum cut;
    • evaluate_pairs: many independent pairs, in parallel;
    • evaluate_edge_failures: how much delivery each edge's failure takes away.
  • Failing calls change nothing. A failing call raises one exception and leaves every ledger, policy and cache as it was; the README names the two limits. Native work releases the GIL.
  • Documentation. docs/semantics.md states each rule, with worked examples that the tests run. The type stub is generated from the compiled module.
  • Proofs. proofs/ holds machine-checked Lean proofs of the rule that splits whole units among members. CI checks them; building or using the package needs no Lean.
  • Timing. dev/perf/ compares two builds on one machine, timing the reference, the candidate, then the reference again.

Checks

  • CI on this PR:
    • lint and the C++ and Python tests on Ubuntu, macOS, Windows and CentOS Stream 9;
    • C++ with MSVC;
    • C++ under ASan with UBSan, and under ThreadSanitizer;
    • the type stub and typed clients;
    • the Lean proofs.
  • NetGraph: the full test suite of NetGraph 0.25.0 passes on this engine (run locally).

🤖 Generated with Claude Code

networmix and others added 30 commits October 3, 2026 18:05
…, from_hops, cost metadata)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…review artefacts

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ow core

Delete the old library (FlowState, the old FlowGraph and FlowPolicy, the backend,
the old search entry points and their bindings: 411 symbols, 65 files) and build
the first piece of the streamlined Core against APPROACH revision 25:

- StrictMultiDiGraph(num_nodes, src, dst, capacity_units, cost, resolution): capacities
  as int64 units of an explicit resolution, costs >= 1, edges in the canonical
  (src, cost, dst, input) order so the forward CSR slot is the public EdgeId,
  input_index() as the only map, to_units (half up) and as_float, validation in the
  contract's order (resolution, unit values, shapes, costs, bounds) before any narrowing.
- The flow core: successive shortest paths with potentials on reduced costs, Dinic
  phases per tier, forward and reverse arcs interleaved in ascending EdgeId at every
  node, virtual terminals, a fresh solve from zero on the view, the two-branch
  behaviour for an input cap of 0, the closure seeded at the real sources; exposed to
  the recorder through the private _place_global_units entry with the typed payload
  (placed, cost_distribution, edge_flows, min_cut, potentials with NEVER_REACHED = -1,
  six int64 counters).
- 38 C++ tests (every validation error, B′ order, the review witnesses: attained value,
  update-before-drain, two Dinic phases in one tier, the legal maximum, the zero-view
  cap, masks and cost factors) and 154 Python tests.

Gates (dev/design/2026-10-streamline/gates/1a/, uncommitted design tree): workspace
check and sanitizers green; independent oracle population 3,821 pass / 0 fail;
cross-check against the Python reference on 30,000 random graphs identical; the
equivalence run against the G0 baseline 0 failures under the 1a manifest rules;
inventory: 0 Core callers of any deleted symbol (NetGraph callers pending until
Phase 2); construction and max-flow A/B/A rows 23 to 48 percent faster than df3b894.
Reviewed adversarially (review-1a, review-1a-fixes): D1 to D4 fixed or settled in the
contract.

NetGraph is red from this commit until its migration converges (Phase 2), by decision
of 2026-10-04.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…search

Build the second piece of the streamlined Core on the 1a graph and flow core, against
APPROACH revision 27:

- FlowGraph: the integer ledger (records as snapshots in FlowId order, residual and
  allocated views, bottleneck over member edges, reset only unbound), the lifetime table
  (bound without flows, bound with flows, orphaned records; copy, move, destroy), epochs,
  ids never reused; a staged, exception-safe commit (every throwing step before one
  no-throw publication, an epoch-stamped touched set, all-or-nothing scratch growth) with
  fault-injection tests.
- The three kernels in units: MaxFlow as a fresh minimum-cost solve on the view
  V_i = R0 + own_i with arcs in B' order; Weighted with membership by priority at
  creation, frozen weights and highest-averages apportionment; EqualBalanced with round
  robin on a fixed cohort; Admission.Lossy on EB and Weighted (per-call offers, transit
  drops, +inf injection); split-graph pruning; T_max by bisection with an exact bracket
  and a checked 62-probe bound.
- PredDAG (from_csr with complete validation, predecessors in ascending via EdgeId),
  Path(g, hops) with to_dag(), Eligibility with intersect.
- The internal search: the unique shortest-path DAG over the eligible member graph,
  anycast pruning, the staged canonical single path with PathSelection projection.
- Bindings, _docs.py and stubs for the public classes; 100 C++ tests and 200 Python tests.

Gates (dev/design/2026-10-streamline/gates/1b/, uncommitted design tree): workspace
check and sanitizers green; oracle population 3,821 pass / 0 fail; check_spf
3,820/3,820; prototype cross-check identical on 45 fixtures and 16,172 random cases;
909 corpus Manual placements equal their references; the equivalence run 0 failures
under the 1b rules (no observable change before the policies of 1c); inventory: 0 Core
callers of any deleted symbol; A/B/A: construction and max-flow rows 22 to 47 percent
faster than df3b894, the five kernel rows 5 to 36 percent faster after the one-pass
PredDAG-native route and plan builder; a per-process bimodality of the
proportional-grid row (an out-of-line NodeMap::add in the flow core's subset solve whose
callee-saved register reloads stalled in some process layouts) fixed by an allocation-free,
noexcept, inlined add with a guard test, both grid rows unimodal (-33 and -4 percent).
Reviewed adversarially (review-1b, review-1b-fixes, review-1b-fixes2,
review-1b-bimodality): D1 to D3, N1, N2 and R15 fixed; D3 settled in the contract
(revision 26), R16 in revision 27.

NetGraph stays red until its migration converges (Phase 2).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…to 1c tree coherent and groomed

Build the third piece of the streamlined Core on the 1a graph, the 1b ledger and kernels,
against APPROACH revision 42, then make the whole 1a to 1c tree coherent and groom its public
interface before 1d:

- FlowPolicy with the four routing modes (CostOnly, CapacityAware, Global, Pinned),
  DiminishingReturns, the cost ceiling (Absolute, Stretch), explicit placement, admission and
  selection; place(volume, outputs) -> PlacementResult (placed, remaining, termination,
  local outcomes per roster source, the two event-accumulating flags, drops, the cumulative
  cost curve, edge flows, residual, bottleneck, min cut); remove(), records(), record(id),
  placed_demand(), usable_bundles(); the roster fixed at construction (9.1); the per-source
  transaction of 9.2 (cumulative round-robin offers, stages 1 to 4 on a per-thread working
  overlay, growth never repeating a route on a multipath DAG, T_max probes within the
  62-probe bound); the termination reduction and the flag table of 9.4; the epoch law and
  lifetimes of section 8; Global as the cumulative minimum-cost flow on V = W + own with the
  canonical decomposition and per-source records; every output prepared before one atomic
  commit, including the Python result objects (all-or-nothing across worker threads and the
  binding, APPROACH 13, with its two stated limits: the pybind11 object machinery, and a
  supplied building DagCache as a memo).
- Sparse record bundles over member nodes (R16): the public CSR shape on demand, unchanged
  supplied bundles shared, the builder's grouping by a stable counting sort.
- DagCache in building mode: key (root set, direction, Eligibility uid), value the full
  static support, statistics (hits, misses, entries, bytes, live values), clear().
- Evaluators: capacity, batch_capacity (arguments first, then each pair completely in pair
  order; the lowest failing index; explicit per-pair sequences length-checked; Pinned
  rejected), sensitivity (Global positive-flow candidates, the other models every allowed
  edge of at least one unit, signed losses, unevaluable with a reason); workers with
  private caches allocated before any thread starts, noexcept worker bodies, every thread
  joined before the lowest-index failure is rethrown; the exact shortcuts (Global loss from
  the baseline's maximum flow, CostOnly off-route edges skipped).
- One min-cost flow core with the deletion re-solve as a mode (APPROACH 2.2): the deletion
  state as a value, working potentials as published potentials plus the running sum of the
  deletion deltas, 128-bit working arithmetic within the stated bounds, the three
  fixed-origin repairs with the failed-search reuse; it replaces the separate warm solver
  and the 1b single-bundle driver (_PinnedFlow, src/pinned_flow.cpp); a one-bundle Pinned
  FlowPolicy stands in for the latter.
- The coherence pass of the whole tree: no dead code, no wrong comment, no leaked
  abstraction, one implementation per rule; profiling removed; the test probes and trace
  behind the CMake option NETGRAPH_CORE_PRIVATE_HOOKS (OFF for wheels, ON for the
  development build and the tests; the OFF build exports no hook, probe or trace symbol);
  transactional copy assignment of the deletion state; validating constructors for every
  enumeration-typed option (std::invalid_argument naming the field).
- The public interface groomed to one scheme: Graph (was StrictMultiDiGraph), FlowLedger
  (was FlowGraph) taken by FlowPolicy as ledger=, OutputOptions (was Outputs),
  DiminishingReturns (was Diminishing), MAX_FLOWS_PER_CALL in Python (kMaxFlowsPerCall in
  C++); the public headers split so each says what it holds (graph.hpp, eligibility.hpp,
  flow_record.hpp, flow_ledger.hpp, flow_policy.hpp, evaluators.hpp, dag_cache.hpp,
  options.hpp with the two placement enums, pred_dag.hpp, types.hpp); Counter128
  self-contained, so no public header includes a detail/ header; the private headers and
  the test files on one naming scheme; the policy fixture data in tests/data/ with a
  maintained exporter; no alias, no re-export, no adapter of a retired spelling.
- The kernel's round robin as one inline callback template (one implementation of the split; the
  span form a wrapper), so allocate and the Lossy step run in one pass: the coherence pass's
  two-pass form had cost kernel.eb.grid 11 percent against df3b894; results unchanged.
- The kernel plan builder fills its arrays by index into pre-sized vectors instead of push_back:
  under Xcode 27's libc++ 22 the push_back form cost the kernel rows about ten percent
  (emplace_back's new fast-path and slow-path lambdas); results unchanged.
- Bindings, _docs.py stubs and the Python package for every class and function
  (keyword-only configuration objects, keep-alive of ledger, Eligibility and cache, the GIL
  released in the evaluators); pytest collects tests/ only; 157 C++ tests (134 ordinary,
  23 in the separate fault executable) and 266 Python tests, with fault-injection sweeps
  of every growth, publication and deletion path at 1, 2 and 4 workers and at every Python
  allocation position.

Gates (dev/design/2026-10-streamline/gates/1c/, uncommitted design tree), on the final
groomed build with the private hooks on and off: workspace check and sanitizers green; the
fault executable under the sanitizers; the warning-set builds with 0 diagnostics; the
literal equivalence run 0 failures (31,788 unchanged, 28,224 new-api, 14,778 rule, 13,701
new-only; the rest invalid-input, deferred to 1d or absent by rule); oracle population
13,901 pass / 0 fail (ksp and spf queries wait for 1d); every returned value checked and
proven by mutation (114,222 corruptions of the accepted results, 0 undocumented survivors);
inventory CHECK OK on 1,249 rows, 0 Core callers of any deleted symbol; prototype parity
identical on the 1b and 1c drivers (45 and 59 fixtures, 8,412 random cases); every recorded
result of the groomed build identical to the pre-grooming build's (20,960 of 20,960) and to
the frozen 1c baseline; A/B/A against df3b894 on the final tree (both sides Apple clang 21 build
2100.3.34.2, Xcode 27), 15 of 15 rows: construction -43 and -52 percent, the kernels -3 to
-28 (kernel.eb.grid -1.8 within tolerance), capacity -28 and -24, max-flow -13 and -14,
sensitivity -8 (the deletion re-solve replacing the separate warm solver) and -92, the
CapacityAware cohort rows -61 and -99.6; the new-model rows as target-versus-target A/A, 3 of 3. Reviewed adversarially (review-1c, review-1c-fixes, review-1c-r29,
review-resolve 1 to 3, review-audit-1abc, review-coherence, review-coherence-rc,
review-naming 1 to 4, review-grooming, review-timing-fixes): F1 to F5 fixed (a worker-thread allocation that
escaped to std::terminate, Python publication after the commit, batch validation order, the
explicit empty eligibility list, the Global zero call's cumulative curve); the coherence
audits' findings fixed and RC1 to RC3 closed; the tooling's twelve gate holes (W11) closed
by W12 before these gates ran.

NetGraph stays red until its migration converges (Phase 2).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Weighted quota division: a 64-bit division when the dividend fits
  (kernel.weighted.clos 6 to 8 percent faster).
- Graph constructor: stop computing the GCD once it reaches 1
  (construct.clos about 4 percent faster).

Both measured A/B/A against the 1c build; every result byte-identical;
a test for each.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
One traversal core now runs every search. The public spf searches from a
set of roots, forward or reverse, optionally stopping at the nearest
target. ksp lists the K shortest paths in the contract's order, and
resolve_to_paths unfolds a DAG into paths. The DagCache gains a frozen
mode that every worker of an evaluator can share. Hits are published
when the call succeeds; a call ending in DagCacheMiss counts one miss.
The Python bindings cover all of it, and the old search entry points are gone.

CostOnly placement builds a new flow's route, record DAG and kernel plan
in one pass over the cached support, with a few smaller cuts. NetGraph's
MSD loop now takes about 3 % less time than 0.12 instead of 35 % more;
results are unchanged.

Evidence: after the independent review's fixes, the final gates pass
with hooks on and off; the timed SPF rows take 9 to 82 % less time than 0.12.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Graph constructor numbers the edges with stable counting passes
instead of a full sort. Costs are ranked through a hash table that
reuses the graph's own arrays; above 65,536 distinct costs, or when the
table probes too long, the costs are sorted instead.

Results are unchanged; construction takes 21 to 91 % less time than
0.12. An empty NumPy view from a graph accessor now also keeps its
graph alive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bindings are split by subject, the type stub is generated from the
module and checked, and docs/semantics.md states the API's rules, each
with a test. A double free when a result object could not be wrapped
is fixed, and inputs too large for the flow core are now refused.
Dead code and the old research and timing material are gone.

Results match accepted 1e with the private hooks on and off; only the
field that names the producing build differs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One driver times the Core's rows as old build, new build, old build
again on one machine, so drift shows, and checks that both builds give
the same results. Its own tests run with make perf-tool-test and on one
macOS CI job, so a library change that breaks the tool fails there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The private test hooks build on Python 3.11 (no PyErr_GetRaisedException
  before 3.12) and keep the exception object and its traceback.
- The rank oracle uses += so the portable 128-bit test integer (MSVC) compiles.
- The two large fault sweeps get a 600 s ctest timeout; they finish but run
  long under sanitizers on small CI machines.
- The native allocation sweep accepts a failure that the standard library
  absorbs (GNU stable_partition's optional buffer) only with the exact
  reference result, state and live count; its discard control now compares
  a real reference result.

Library code and results are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The few-allocations control plants a call that makes no native
  allocation on any platform; reset's count depends on the standard library.
- The Python allocation sweep's controls advance at snapshot boundaries, so a
  tracer that fails before a call enters its body cannot shift them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- spf and ksp never read capacity; only an explicit residual or a mask
  removes an edge. HEADROOM without a residual reads 1, or 0 on a
  zero-capacity edge.
- CostOnly supports, rosters, cached DAGs and Stretch baselines keep
  zero-capacity edges. EqualBalanced still sends them their share;
  Weighted gives them weight 0 and never routes around them.
- Pinned bundles follow the same kernel member rule.
- Sensitivity lists every allowed edge; a deletion loss can be negative.
- Global's minimum cut lists every allowed edge leaving the source side,
  zero-capacity ones included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A demand's sources are pooled: CostOnly ECMP and WCMP split the summed
  traffic once at a shared router and attribute it back to each source by
  highest averages.
- Global and CostOnly MAX_FLOW take an origin: OPTIMIZED (one virtual
  source) or EQUAL (the largest total whose round-robin shares all fit).
  CostOnly MAX_FLOW is one solve over the union of the sources' routes.
- Results drop local_outcomes and gain per-source rows, the epoch
  accounting, total_cost and the call_* fields; min_cut has its own
  request flag; NO_ACTIVE_WEIGHT is a termination.
- One QueryOptions replaces CapacityOptions and SensitivityOptions;
  FlowRecord.placement is gone.
- Timing rows for pooled sources and the equal origin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Every computing model records what it decided as a ForwardingProgram:
  tunnels and split routes with whole-number weights and equal or captured
  source injection; controllers cut their solved flow into paths.
- ForwardingProgram.from_routes builds one from given routes; Pinned forwards
  one without computing, replayed under its mask or stale with black holes.
- Global(support=dag); WCMP weights from WeightBasis.
- evaluate, replace and forward; program-based flags and per-entry records;
  by_tunnel and FlowRecord.entry; QueryOptions.offered.
- Lossy forwarding inside one program uses nested highest averages at shared
  links and recalculates cycles in rounds, with a check or an explicit error.
- Weight sums are exact beyond 64 bits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CapacityAware is now T1: every head splits its design share over N strict
tunnels, each set up by CSPF on the unreserved view and holding its whole
request, or down. The tunnels form a forwarding program, so Lossless, Lossy,
replay and stale forwarding all work on them. The ledger keeps carried traffic
and strict reservations as two separate projections. The old growth model and
its options, terminations and reference fixtures are gone.

Timing against the previous step: 22 rows within -7 % to +1.5 %, identical results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Global(tunnels=Tunnels(N)) lays out the same number of single-path tunnels
for every live head with one common whole-unit bandwidth: as large as
possible, no larger than a finite design needs, on the cheapest routes, then
with prefer_headroom on the routes that leave the most spare capacity, then
one fixed tie; Tunnels(L, up_to=True) picks the smallest sufficient count.
The tunnels form a forwarding program, with Tunnel.planned, T2's TunnelPlan
fields, evaluators, sensitivity and failure replay. Every Global now rejects
a path selection; the headroom objective is its own option.

Timing against the previous step: 24 rows within -1.2 % to +1.6 %, identical results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every public and internal name is now final, with no alias left. Each routing model
holds its own options (FlowPolicyConfig(routing, admission)); ShortestPaths() is ECMP,
CentralizedTE states its source allocation, and a wrong type or a misplaced option is a
TypeError when the object is built.

One tie rule, RouteTieBreak, replaces PathSelection and EdgePriority. CostStretch is an
exact fraction, CentralizedTE takes a support together with a tunnel count, every
evaluator takes a finite offer, and ForwardingProgram.from_dag shares ShortestPaths'
pooled builder. Enum getters and FlowPolicy.config no longer crash under a Python
allocation failure.

155 of the contract's 168 examples run natively in C++ and Python; one is outside the input
domain and 12 wait for steps 5 and 6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An independent audit of steps 1 to 4 in four dimensions found these, all fixed here: the
Python binding of the tunnel models checked an offer before refusing the operation; an
allocation failure while reading an integer, a fraction, a list result or a hash raised
TypeError or RuntimeError instead of MemoryError; identity counters and the forwarding
network's 32-bit positions now refuse instead of overflowing; retired empty policies no
longer stay in the owner registry. Two rules implemented twice now live in one place, an
old split strategy that only tests reached is gone, public texts no longer promise more
than the code does, and the removed-name test reads every kind of text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every public entry now raises TypeError for an argument of a type it never accepts and
ValueError for an accepted type with a value outside its domain; the texts stay as they
were. Integer positions take any exact integer (an object with __index__, never a bool);
positions that take a non-integer number take exactly int, float and NumPy numeric scalars,
or a sequence or array of them, so a Decimal, a Fraction outside CostStretch or a bool
among numbers is refused. The converter is one classifier with a stated order, and an
allocation failure inside it is a MemoryError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
apply_actions(ledger, actions, mode=...) runs Place, Replace and Forward actions together.
ORDERED gives what the same calls one by one would give and publishes once, so an error
changes nothing. SIMULTANEOUS forwards every participant at the same time on fixed routes:
demands share the losses on each link, cyclic losses are solved in rounds as within one
program, and a participant's black holes are its own. Black-holed traffic is dropped where
it arrives in single calls too, and a tunnel or route member that can carry nothing no
longer makes a link wait. A list too large for the forwarding counters is refused with an
error that names the counter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contract r9 amendment 8: an occurrence that can carry nothing (a share of 0, a member whose
whole-unit share is 0, or traffic behind a black hole of its own call or participant) takes no
part in the loss rule's no-drop loads, its waits or its serial normalization. Adding such a
demand, or a pooled source whose share is 0, no longer turns another demand's result into a
loss-iteration error or the other way round. Results without such an occurrence are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every operation now converts and checks its arguments with Python's lock held, then releases
it for native work: at most one cache lease, then the ledger's single gate, checks of mutable
state, private computation and a publication that cannot fail. Calls on one ledger appear one
at a time and different ledgers run in parallel; reads of immutable values take no lock. Cache
freeze, thaw and clear are served in arrival order, so they are never starved. The
free-threaded build relies on the gate and the leases alone.

A call with a bad argument and a wrong state now reports the argument's error first. The
frozen-cache clear message no longer asks callers to join readers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
to_units rounds the binary64 value it is given half up, never its printed decimal. The
documents and docstrings now state that rule exactly (the ties, the 2^52 and 2^53 steps,
the round trip, the error of one value and of a sum), the conversion types follow contract
r9, and native and Python tests pin every example. Results do not change.

as_float reads an int or a NumPy integer, a subclass included, as one number before it tries
the argument as a sequence, as to_units already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sensitivity of the unrestricted optimized CentralizedTE maximum flow now reads each loss from the
baseline's residual network: a saturated edge between different strongly connected components
loses its whole flow, any other stops its repair once the loss is known. Every candidate still
starts from the baseline and nothing intermediate is published.

K shortest paths keeps the spur index that produced each alternative (Lawler) and skips the
searches it has already done. Paths, their order and every field are unchanged.

One strongly-connected-components routine now serves both sensitivity and lossy forwarding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The A/B/A harness builds now use -falign-functions=64 on the reference and the candidate, so code
placement moves timings less (global.equal.random's spread between two layouts of the same source
fell from 3.5 % to 0.1 %). Wheel build flags are unchanged.

Step 8 measured, and did not adopt, reuse across failure states and Global solver structure (below
the 10 % profile trigger), PGO (gains under 5 %, held-out rows slower) and -march=native.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two crashes at legal limits are gone: an integer NumPy array whose cast runs out of memory now
raises MemoryError, and pooled forwarding keeps its head-label offsets at full width. The
Windows test build compiles again, ThreadSanitizer runs in CI, and the failure-test comparator
checks every result field. One sparse-vector helper and one weight converter replace their
copies, a dead trace variant is removed and internal names follow the glossary. Documents,
docstrings and stub signatures now say what the code does. Results do not change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ShortestPaths policy whose routes and weights read only fixed values (FEWER_HOPS or
HIGHER_CAPACITY, EQUAL or WEIGHTED with capacity or explicit weights) now keeps the program it
first prepared and uses it again after remove() and at replace(), instead of building it again.
Choices that read free capacity still prepare afresh. Results equal a fresh preparation in every
field; no API, cache or statistics change. NetGraph's MSD search (one ledger per series, remove
before each probe) runs about twice as fast on the new msd.loop.epochs timing row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CentralizedTE without tunnels gains SourceAllocation.WEIGHTED with an explicit
source_weights vector. At every total the sources inject exactly their
highest-averages shares of that total; the controller carries the largest total
it can at those shares, then the cheapest flow. The program keeps the declared
weights, so a replay at another offer keeps the same proportions. NetGraph's
controller uses it to share a destination among sources in proportion to their
demands. EQUAL and OPTIMIZED results do not change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The semantics reference, README, binding docstrings (and the generated
stub) explain each rule in plain words first, then exactly, with small
worked examples that run as shown. Statements that were false or too
broad are corrected, implementation notes are replaced by the guarantees
they serve, and the 0.12.0 changelog says what changed against 0.11.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
networmix and others added 4 commits October 25, 2026 21:38
Adds a Lean project with machine-checked proofs of the rule that splits
whole units among members in order and of its cumulative increments, a
plain explanation of each theorem with worked examples, a checker that
pins every statement, CI that verifies the proofs, and a release step that
publishes only the files whose proofs were checked. Nothing changes at run
time, and the wheel does not need Lean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README says what the engine is for, its main ideas and a short
Quick Start whose printed results the tests run; links are absolute so
they work on PyPI. The 0.12.0 changelog splits long entries and says
each change in plain words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove the tests that asserted the wording of the README, changelog,
docstrings and comments: the search for names removed in 0.11 with its
list of exceptions, the docstring name census and literal sentence
checks. The proof gate no longer fingerprints the prose it cites; it
still checks the cited headings and anchors, the native source, and
every proof. Documented examples still run and print what the pages
show.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The changelog's unreleased section becomes 0.12.0, dated today.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@networmix networmix changed the title Streamline: the rebuilt Core (phases 0 to 1e and the cleanup) NetGraph-Core 0.12.0: the rebuilt engine Oct 27, 2026
networmix and others added 5 commits October 27, 2026 03:54
The C++ test helper `finite` is renamed `design_of`: the GNU C library
declares a global `finite(double)`, so calls with an integer were
ambiguous on Linux. The test guard asks a free-threaded run to skip the
GIL-only tests only when it collected them, and reports an unexpected
skip in the same words on every build. Two concurrency tests no longer
call `sys._is_gil_enabled`, which Python 3.11 and 3.12 lack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test that checks engine calls release the GIL needs the main thread
to see at least one call of each operation in its native phase. On CI's
CentOS Stream 9 with Python 3.11, 60 calls of a short operation could
finish before the main thread looked. Each operation now runs up to 50
batches of 60 calls, and a sighting counts only when two snapshots show
the same unfinished call; a call that keeps the GIL still fails it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t cleanly

On Python 3.11-3.13, repr() and hash() turn a failed method lookup into
the default repr or a TypeError, so the sweeps now call the type's own
method. Two of our reprs used repr() for a nested value and could return
a wrong string with no error; they now raise MemoryError. The Python
allocation hook suspends a coverage tracer for the call, the native
sweep allows a free made inside libc++, the address filter accepts
Windows' upper-case hex, and each fault test gets ten minutes under
sanitizers.

On Linux with Python 3.11 to 3.13.7, a daemon thread whose engine call
or policy destructor ended during interpreter exit crashed the process.
Such a thread now waits forever, as newer Pythons do, and the exiting
thread still retakes the GIL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guard's list of tests that skip without the private hooks names the
renamed repr tests and the new tracer test. The daemon-exit test holds
module teardown with a clock loop: on Windows, time.sleep fails there
because Python has already closed the event it waits on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One fault sweep took 563 seconds under AddressSanitizer on a CI runner,
then passed the ten-minute limit on a slower one with the same code.
Each fault test now gets thirty minutes; every attempt is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@networmix
networmix merged commit e228820 into main Oct 27, 2026
39 checks passed

This branch was successfully deployed

1 active deployment
pypi — e228820c Deployed Oct 27, 2026 by networmix via Publish to PyPI #59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant