Skip to main content

Almost-linear minimum-cost flow (Python + Rust)

Project description

almo-mcf

Almost-linear minimum-cost flow is the research goal for this project. The current implementation ships an exact min-cost flow solver with a Python + Rust interface. The Python API mirrors NetworkX conventions while the Rust core provides an integer min-cost flow solver with lower bounds and node demands.

The solver now uses an IPM-style potential reduction method by default on larger instances, with rounding to exact optimality. Small instances and IPM non-convergence fall back to the classic successive shortest path routine, so the output stays exact even when the dynamic oracle is conservative.

Features

  • Exact min-cost flow for directed graphs with:
    • node demands (sum must be zero)
    • per-edge lower/upper capacities
    • integer edge costs
  • IPM + rounding pipeline with min-ratio cycle updates
  • Capacity and cost scaling for large (U/C) bounds
  • NetworkX-compatible adapter (min_cost_flow, min_cost_flow_cost)
  • Rust core with Python bindings via maturin
  • Solver telemetry (iterations, gap, termination) when requested
  • Deterministic, reproducible results with an opt-out randomized mode

Installation

pip install almo-mcf

The PyPI package ships a Rust extension module. If you are installing from source, you need a Rust toolchain (stable) and a C compiler.

Release prep: the almo-mcf name is available on PyPI (verified via a 404 on https://pypi.org/project/almo-mcf/).

From source (editable)

# from the repository root
python -m pip install -U maturin
maturin develop

Quickstart (NetworkX)

import networkx as nx
from almo_mcf import min_cost_flow, min_cost_flow_cost

G = nx.DiGraph()
G.add_node("s", demand=-3)
G.add_node("t", demand=3)
G.add_edge("s", "t", capacity=5, weight=2)

flow = min_cost_flow(G)
print(flow)
print("cost:", min_cost_flow_cost(G, flow))

Examples

See examples/almo_usage.py for a longer walkthrough and examples/networkx_quickstart.py for a concise NetworkX entrypoint example.

IPM tuning + stats

from almo_mcf import min_cost_flow

flow, stats = min_cost_flow(
    G,
    use_ipm=True,            # force IPM path (set False for classic SSP)
    use_scaling=None,        # auto-detect large U/C, set True/False to override
    deterministic=True,      # default; set False for randomized cycle selection
    strategy="periodic_rebuild",
    rebuild_every=25,
    max_iters=250,
    tolerance=1e-9,
    seed=42,
    threads=2,
    approx_factor=0.2,
    return_stats=True,
)
print(stats)

Almost-Linear Mode

The solver defaults to the almost-linear IPM path on larger instances. The use_ipm flag forces the solver path, while deterministic=True disables randomized sampling in the dynamic oracle to match the derandomized pipeline from Paper 2. When return_stats=True, the stats dictionary includes a solver_mode key so you can log whether the run used ipm, ipm_scaled, classic, or classic_fallback.

flow, stats = min_cost_flow(
    G,
    use_ipm=True,       # prefer IPM for large graphs
    deterministic=True, # reproducible shifting instead of random sampling
    return_stats=True,
)
print(stats["solver_mode"])

Scaling for large capacity/cost bounds

When inputs contain very large capacities or costs, the solver automatically enables capacity/cost scaling to keep the IPM rounds within polynomial bounds. You can override this behavior via use_scaling=True or use_scaling=False, or call the explicit helper min_cost_flow_scaled.

For numerically extreme instances (very small residual slacks or huge U/C bounds), the Python API exposes clamp controls such as numerical_clamp_log, residual_min, and barrier_clamp_max. These guard the barrier and gradient computations against overflow/underflow. When experimenting with research-grade settings, enable log_numerical_clamping=True to see how often the solver clamps values.

Deterministic vs. randomized solver behavior

Deterministic mode is enabled by default, which fixes the min-ratio cycle updates and dynamic sparsification choices for reproducibility. To experiment with the original randomized behavior (useful for performance comparisons), set deterministic=False on solver calls such as min_cost_flow or the extension reducers like min_cost_flow_convex and max_flow_via_min_cost_circulation.

When deterministic mode is enabled, the solver uses stable tie-breaking on edge and node IDs, deterministic sparsification, and lexicographically smallest path embeddings. You can optionally provide deterministic_seed to influence tie-breaking hashes for debugging; this does not enable random sampling. The stats dictionary returned with return_stats=True includes deterministic_mode_used and seed_used so you can log reproducibility metadata.

Extensions (convex costs, max-flow, isotonic regression)

import networkx as nx
from almo_mcf import min_cost_flow_convex, max_flow_via_min_cost_circulation

G = nx.MultiDiGraph()
G.add_node("s", demand=-2)
G.add_node("t", demand=2)
G.add_edge("s", "t", capacity=2, convex_cost=[0, 1, 6])
G.add_edge("s", "t", capacity=2, convex_cost=[0, 2, 4])
flow = min_cost_flow_convex(G)

H = nx.DiGraph()
H.add_edge("s", "t", capacity=5)
max_flow_value, max_flow = max_flow_via_min_cost_circulation(H, "s", "t")

Supported NetworkX attributes

  • Graph type: nx.DiGraph and nx.MultiDiGraph
  • Node attributes:
    • demand (int)
  • Edge attributes:
    • capacity (required, finite)
    • lower_capacity (optional, default 0)
    • weight or cost (int; weight is preferred to match NetworkX)

The output format matches networkx.min_cost_flow: dict[u][v] = flow.

Lower-level array API (Rust core binding)

If you need to avoid NetworkX overhead, you can call the Rust extension directly:

import numpy as np
from almo_mcf import _core

flow = _core.min_cost_flow_edges(
    n,
    np.asarray(tails, dtype=np.int64),
    np.asarray(heads, dtype=np.int64),
    np.asarray(lower, dtype=np.int64),
    np.asarray(upper, dtype=np.int64),
    np.asarray(cost, dtype=np.int64),
    np.asarray(demand, dtype=np.int64),
)

# Force scaling in the core solver (useful for very large U/C bounds).
flow, stats = _core.min_cost_flow_edges_with_scaling(
    n,
    np.asarray(tails, dtype=np.int64),
    np.asarray(heads, dtype=np.int64),
    np.asarray(lower, dtype=np.int64),
    np.asarray(upper, dtype=np.int64),
    np.asarray(cost, dtype=np.int64),
    np.asarray(demand, dtype=np.int64),
)

Inputs must be integer arrays with consistent lengths.

Limitations (current)

  • All capacities must be explicit and finite.
  • Performance is currently tuned for correctness and clarity, not large-scale instances.

Repository layout

python/almo_mcf/        # Python API + NetworkX adapter
rust/crates/almo-mcf-core/   # Rust solver core
rust/crates/almo-mcf-py/     # PyO3 bindings
tests/                 # pytest suite (NetworkX parity + regressions)

Development

Run tests

pytest -q

Benchmarks

There are benchmark scripts that compare IPM vs the classic successive shortest-path (SSP) routine across sizes and capacity/cost ranges:

python tests/bench_ipm_vs_ssp.py --runs 3 --nodes 30 60 --edges 150 300 --capacity 10 50 --cost 5 20
python tests/bench_ipm_vs_networkx.py --nodes 60 --edges 300 --runs 3

Build the Rust core

cargo build -p almo-mcf-core

Roadmap highlights

  • Integrate the IPM solver and min-ratio cycle oracle from DESIGN_SPEC.md.
  • Add dynamic oracle updates and almost-linear data structures.
  • Expand benchmarks and performance tuning.

References

The IPM + min-ratio cycle approach follows the ideas in:

  • Li Chen, Rasmus Kyng, Yang P. Liu, Richard Peng, Maximilian Probst Gutenberg, and Sushant Sachdeva. Maximum Flow and Minimum-Cost Flow in Almost-Linear Time. arXiv:2203.00671, 2022. https://arxiv.org/abs/2203.00671
  • Jan van den Brand, Li Chen, Rasmus Kyng, Yang P. Liu, Richard Peng, Maximilian Probst Gutenberg, Sushant Sachdeva, and Aaron Sidford. A Deterministic Almost-Linear Time Algorithm for Minimum-Cost Flow. arXiv:2309.16629, 2023. https://arxiv.org/abs/2309.16629

License

Apache 2.0. See LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

almo_mcf-0.2.0.tar.gz (129.1 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

almo_mcf-0.2.0-cp310-abi3-win_amd64.whl (457.2 kB view details)

Uploaded CPython 3.10+Windows x86-64

almo_mcf-0.2.0-cp310-abi3-manylinux_2_34_x86_64.whl (638.0 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.34+ x86-64

almo_mcf-0.2.0-cp310-abi3-macosx_11_0_arm64.whl (544.3 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

File details

Details for the file almo_mcf-0.2.0.tar.gz.

File metadata

  • Download URL: almo_mcf-0.2.0.tar.gz
  • Upload date:
  • Size: 129.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for almo_mcf-0.2.0.tar.gz
Algorithm Hash digest
SHA256 b52dd77360b8ef7c117fbf39405dc71eff8cfd525b9d67bae4c06d1c4d5dbc84
MD5 9fcf3549bbea8915043cda8a0adcd104
BLAKE2b-256 1d4df3b55f3561d2a9649679b3d426a889597a6fc336d0987e44424f8c61dc03

See more details on using hashes here.

Provenance

The following attestation bundles were made for almo_mcf-0.2.0.tar.gz:

Publisher: release.yml on tripp-smith/almo-mcf-rs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file almo_mcf-0.2.0-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: almo_mcf-0.2.0-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 457.2 kB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for almo_mcf-0.2.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 bfe0a332456966b019db93ed0ccce52864d9b780d240dcbd3f7c8e7a409f1be2
MD5 d4fa394735322480246326da481a24a6
BLAKE2b-256 92e1950b20fef77021bfcebce0482530e39ed5498797361dc76ccf4689b67178

See more details on using hashes here.

Provenance

The following attestation bundles were made for almo_mcf-0.2.0-cp310-abi3-win_amd64.whl:

Publisher: release.yml on tripp-smith/almo-mcf-rs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file almo_mcf-0.2.0-cp310-abi3-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for almo_mcf-0.2.0-cp310-abi3-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 f4100ce18fe89b9ed74fcab655e2f6b28ad2ff57610c9a22365d83c7f57a63e0
MD5 00d250c27d8602e6873b1ad98680b55c
BLAKE2b-256 22c90c5803b4aa4c3bc1344c2fbc4ed6b824213f10aa54336ee071fd7dbbe883

See more details on using hashes here.

Provenance

The following attestation bundles were made for almo_mcf-0.2.0-cp310-abi3-manylinux_2_34_x86_64.whl:

Publisher: release.yml on tripp-smith/almo-mcf-rs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file almo_mcf-0.2.0-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for almo_mcf-0.2.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 ac2f227dc8291b97620e00baa94cc3a6b1aca697a643742224b9e2bb1f8dc6d2
MD5 ece45a9c6101553dbee06ca62f68072b
BLAKE2b-256 737f8aea0e5e3f81d844233dfbee24d007f65726c333099c003a877013e6c975

See more details on using hashes here.

Provenance

The following attestation bundles were made for almo_mcf-0.2.0-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on tripp-smith/almo-mcf-rs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page