Skip to main content

Package to solve Steiner Tree and Steiner Forest Problems with the HiGHS solver

Project description

SteinerPy

PyPI version PyPI - Downloads Python 3.8+ License: MIT CI/CD codecov

A Python package for solving Steiner Tree and Steiner Forest Problems — and several advanced variants — using the HiGHS solver and NetworkX graphs. Gurobi is also supported as an alternative solver via lazy-cut callbacks.

Installation

Install SteinerPy from PyPI:

pip install steinerpy

Or using uv:

uv add steinerpy

Requirements

  • Python 3.8+
  • NetworkX
  • HiGHS Solver (via highspy)

To use Gurobi as the solver, you additionally need:

  • gurobipy (install with pip install gurobipy)
  • A valid Gurobi license

Quick Start

import networkx as nx
from steinerpy import SteinerProblem

# Create a graph
G = nx.Graph()
G.add_edge('A', 'B', weight=1)
G.add_edge('B', 'C', weight=2)
G.add_edge('C', 'D', weight=1)

# Define terminal groups
terminal_groups = [['A', 'D']]

# Solve with HiGHS (default)
problem = SteinerProblem(G, terminal_groups)
solution = problem.get_solution()

print(f"Optimal cost: {solution.objective}")
print(f"Selected edges: {solution.selected_edges}")

# Solve with Gurobi (requires gurobipy + license)
solution_gurobi = problem.get_solution(solver="gurobi")
print(f"Gurobi optimal cost: {solution_gurobi.objective}")

Supported Problem Variants

Every variant below can be solved as a Steiner Tree (a single group of terminals that must all be connected) or as a Steiner Forest (multiple independent groups, each connected within itself but not necessarily to other groups). Simply pass a list of terminal lists to terminal_groups — one list for a tree, multiple lists for a forest.

Variant Class Description
Steiner Tree / Forest SteinerProblem Classic minimum-cost subgraph that connects one or more groups of terminals.
Prize-Collecting Steiner Tree / Forest PrizeCollectingProblem Each terminal carries a prize; the solver balances the prize collected against the edge and penalty costs, so not all terminals need to be connected.
Node-Weighted Steiner Tree / Forest NodeWeightedSteinerProblem Nodes carry costs instead of (or in addition to) edges. Internally uses a node-splitting transformation to convert the problem to a standard edge-weighted formulation.
Maximum-Weight Connected Subgraph MaxWeightConnectedSubgraph Finds a connected subgraph maximising the total node weight. Nodes with negative weights are included only when they are needed as connectors. Convenience subclass of PrizeCollectingProblem.
Directed Steiner Tree (Arborescence) DirectedSteinerProblem The graph is directed (nx.DiGraph) and a designated root node must reach every terminal via directed paths.

Optional Constraint Modifiers

The two side-constraints below are optional keyword arguments that can be combined freely with any problem class above:

Modifier kwarg Effect
max_degree=k Every node in the solution has at most k incident edges.
budget=B Total edge cost may not exceed B; the solver maximises the number of connected terminals and returns a BudgetSolution.
# Degree-constrained Steiner tree
solution = SteinerProblem(graph, terminal_groups, max_degree=2).get_solution()

# Budget-constrained Steiner tree
solution = SteinerProblem(graph, terminal_groups, budget=10.0).get_solution()

# Both constraints together (previously impossible with dedicated classes)
solution = SteinerProblem(graph, terminal_groups, max_degree=2, budget=10.0).get_solution()

# Degree-constrained prize-collecting problem
solution = PrizeCollectingProblem(graph, terminal_groups, node_prizes, max_degree=3).get_solution()

Note: DegreeConstrainedSteinerProblem and BudgetConstrainedSteinerProblem are still available for backward compatibility but are deprecated — pass the corresponding kwargs to the base class instead.

Solver Selection

Every problem class exposes a solver parameter on get_solution(). Two backends are supported:

solver value Backend Notes
"highs" (default) HiGHS via highspy Always available; cut-based formulation solved iteratively (re-solve loop).
"gurobi" Gurobi via gurobipy Optional; requires gurobipy and a valid Gurobi license. Connectivity cuts are injected as lazy constraints inside a branch-and-cut callback, which lets Gurobi exploit its full branch-and-bound tree.
# Use HiGHS (default — no extra installation required)
solution = SteinerProblem(graph, terminal_groups).get_solution()

# Use Gurobi (requires gurobipy + license)
solution = SteinerProblem(graph, terminal_groups).get_solution(solver="gurobi")

Both solvers implement the same cut-based (DO-D) formulation from Markhorst et al. (2025) and produce identical optimal solutions. Gurobi may be faster on larger instances because callbacks avoid repeated re-solves from scratch.

Usage Examples

See the example.ipynb notebook for detailed usage examples.

Dependencies

  • networkx: For graph representation and manipulation
  • highspy: For optimization solving (HiGHS backend, required)
  • gurobipy: For optimization solving (Gurobi backend, optional — requires a Gurobi license)

If you use this package in your research, please cite:

@article{markhorst2025future,
  title={Future-proof ship pipe routing: Navigating the energy transition},
  author={Markhorst, Berend and Berkhout, Joost and Zocca, Alessandro and Pruyn, Jeroen and van der Mei, Rob},
  journal={Ocean Engineering},
  volume={319},
  pages={120113},
  year={2025},
  publisher={Elsevier}
}

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

steinerpy-1.0.1.tar.gz (27.7 kB view details)

Uploaded Source

Built Distribution

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

steinerpy-1.0.1-py3-none-any.whl (23.3 kB view details)

Uploaded Python 3

File details

Details for the file steinerpy-1.0.1.tar.gz.

File metadata

  • Download URL: steinerpy-1.0.1.tar.gz
  • Upload date:
  • Size: 27.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for steinerpy-1.0.1.tar.gz
Algorithm Hash digest
SHA256 1de1e44f56587f6b00e5df2f3ad24c1bb2cf0c63750649299a07e7b3d736fd39
MD5 7b1186f16a5226850c30cab9298400f9
BLAKE2b-256 1a7dd02afafaa698eb8d0e14a3754abf1f6d3fa2f719e898d2cb30853fbdf939

See more details on using hashes here.

File details

Details for the file steinerpy-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: steinerpy-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 23.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for steinerpy-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d6c207eac94a98452753abd9c9f14bec1e376660b360165c457d52efd445400b
MD5 e938af219edabccdf3f54797a501a0ed
BLAKE2b-256 8043d33f8dd51582835d1b2a15b69e19b655b52d347fa0a2858ca5c0148b56b4

See more details on using hashes here.

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