A Python 3 port of "Network Optimization 3," a mixed-integer programming
(MIP) model for global supply chain network design. This mirrors the
original C++ port's structure — a solver-agnostic problem definition, a
Solver strategy interface, a fast heuristic, a dependency-free exact
solver, and a production-scale MIP path — using PuLP/CBC in place of raw
COIN-OR CBC bindings.
Given a set of customers (demand points, each with a location and a demand) and a set of candidate territory hubs (distribution centers, each with a location, a capacity, and a fixed cost to open), decide:
- Which hubs to open.
- Which open hub serves each customer — i.e. how to draw territory boundaries.
...to minimize total cost: the fixed cost of every opened hub, plus a distance- and demand-weighted service cost for every customer-hub assignment, subject to every open hub's capacity. This is the classic capacitated facility location problem, framed as customer clustering and territory design, trading off service cost against how many distribution centers you commit to running.
For customers i = 1..n and candidate hubs j = 1..m:
x_ij ∈ {0,1}— customeriis served by hubjy_j ∈ {0,1}— hubjis openedcost_ij = distance(i, j) * demand_i * ratePerUnitDistance
minimize Σ_ij cost_ij * x_ij + Σ_j fixedCost_j * y_j
subject to Σ_j x_ij = 1 for every customer i
Σ_i demand_i * x_ij ≤ capacity_j * y_j for every hub j
x_ij, y_j ∈ {0, 1}
The capacity constraint does double duty: it caps how much demand a hub
can serve, and it forces y_j = 1 (paying the fixed cost) before any
customer can be routed through hub j, since x_ij is otherwise
unconstrained.
netopt3.problem.ClusteringProblemowns the customers, the candidate hubs, and the cost rate; it computes distances/assignment costs and validates any candidateClusteringSolution(feasibility + total cost) independently of how that solution was produced — recomputing capacity checks and activation-cost accounting from scratch, the same logic as the C++ClusteringProblem::validate.netopt3.solver.ClusteringSolveris a smallABC(solve,name) somain.pyand the tests can swap backends freely.netopt3.greedy_solver.GreedyNearestHubSolveris a fast largest-demand-first heuristic: place the hardest-to-fit customers first, always to the nearest hub with remaining capacity, opening hubs on demand.netopt3.exact_solver.BranchAndBoundSolveris a from-scratch exact solver with zero external dependencies — stdlib only. It branches over which hub serves each customer, seeds its incumbent from the greedy solution, and prunes with an admissible lower bound (the cheapest possible per-hub distance cost for every not-yet-assigned customer). Exponential in the worst case, intended for the small/medium instances the test suite uses. Because greedy and exact are dependency-free, the full test suite runs with no extra installs.netopt3.pulp_solver.PulpMipSolverexpresses the same model against PuLP targeting CBC — the production-scale path, mirroring the C++ project'sCbcMipSolver.h(there, "documentary," gated behind a CMake build flag needing the CBC dev libraries; here, gated behindpip install pulpinstead).pulpis imported lazily insidesolve(), so importingnetopt3never requires it, and its test is skipped automatically when pulp isn't installed.
pip install -r requirements.txt # optional: only needed for the pulp solver/tests
python3 -m unittest discover -s tests -v
python3 main.pynetopt3/ customer.py, hub.py, problem.py (ClusteringProblem + validate),
solver.py (Solver ABC), greedy_solver.py, exact_solver.py,
pulp_solver.py
tests/ test_clustering.py — feasibility checks, a hand-verified
optimum, greedy-vs-exact comparison, edge cases, and a
pulp-vs-exact cost check (skipped if pulp isn't installed)
main.py demo: greedy + exact (+ pulp, if available) on a sample instance