API reference¶
All public classes are importable directly from the top-level steinerpy
package:
from steinerpy import SteinerProblem, PrizeCollectingProblem, ...
Core problems¶
- class steinerpy.SteinerProblem(graph, terminal_groups, weight='weight', preprocess=True, **kwargs)[source]¶
-
- __init__(graph, terminal_groups, weight='weight', preprocess=True, **kwargs)¶
Initialize the SteinerProblem (can be tree or forest).
- get_solution(time_limit=300, log_file='', solver='highs', dual_ascent=None, exact=True, threads=None, decompose=None)¶
Get the solution of the Steiner Problem.
When
budgetwas supplied at construction time the solver maximises the number of connected terminals subject to the budget constraint and returns aBudgetSolution. Otherwise the solver minimises the total edge cost and returns a plainSolution.Optional modifiers set at construction time (
max_degree,budget) are applied automatically regardless of which problem class is used.- Parameters:
time_limit (float) – time limit in seconds.
log_file (str) – path to the log file.
solver (str) – which MIP solver to use –
"highs"(default) or"gurobi". When"gurobi"is chosen the cut-based formulation is solved with Gurobi lazy-cut callbacks, which requires gurobipy and a valid Gurobi license to be installed.exact (bool) – when
True(default) solve to optimality. WhenFalserun heuristic-only mode: return the dual-ascent primal with no ILP — much faster, and the returnedSolution.gapis a valid optimality gap (0.0⇒ provably optimal). Supported for plain Steiner tree/forest and directed problems only; raisesNotImplementedErrorfor budget/degree-constrained variants.dual_ascent (bool)
threads (int)
decompose (bool)
- Returns:
Solution(orBudgetSolutionwhen a budget is set).- Raises:
ValueError – if an unknown solver name is provided.
ImportError – if
solver="gurobi"but gurobipy is not installed.
- Return type:
- class steinerpy.DirectedSteinerProblem(graph, root, terminals, weight='weight', **kwargs)[source]¶
Bases:
BaseSteinerProblemDirected Steiner Tree Problem (Steiner Arborescence Problem).
The graph is a directed graph (nx.DiGraph). A designated root node must have a directed path to every terminal node. Edges only allow flow in the direction they are defined; reverse arcs are not added.
Prize-collecting and node-weighted problems¶
- class steinerpy.PrizeCollectingProblem(graph, terminal_groups, node_prizes, penalty_cost=1000, penalty_budget=None, **kwargs)[source]¶
Bases:
SteinerProblem- __init__(graph, terminal_groups, node_prizes, penalty_cost=1000, penalty_budget=None, **kwargs)[source]¶
Prize Collecting Steiner Problem - extends regular Steiner problem.
- Parameters:
node_prizes – dict mapping node -> prize value
penalty_cost – cost per unconnected terminal
penalty_budget – maximum total penalty allowed (optional)
- get_solution(time_limit=300, log_file='', solver='highs', pc_transform=None, exact=True, threads=None)[source]¶
Solve the prize-collecting problem.
By default uses the penalty/Big-M flow ILP (HiGHS only). Two opt-in fast paths route through the classic PCSTP/MWCSP -> SAP transformation and the existing dual-ascent + directed-cut machinery:
exact=False— heuristic-only mode: return the dual-ascent SAP primal with no ILP; theSolution.gapis a valid optimality gap (0.0certifies provable optimality).pc_transform=True— exact solve via the SAP transformation with dual-ascent lower bound, reduced-cost fixing, cut-seeding, warm-start and proven-optimal early-exit.
Both require the instance to be a classic forgo-prize PCSTP (or an MWCSP); otherwise a clear
NotImplementedErroris raised.solverselects"highs"(default) or"gurobi"for the transform path’s ILP.
- class steinerpy.NodeWeightedSteinerProblem(graph, terminal_groups, node_weights, weight='weight', **kwargs)[source]¶
Node-Weighted Steiner Tree Problem (NWST).
Converts the node-weighted graph to an edge-weighted graph by splitting each node v into v_in and v_out connected by an edge of cost = node_weights[v]. Then solves the standard Steiner Tree problem on the transformed graph.
Terminal node costs are always incurred (they are part of every solution) and are added as a constant to the reported objective value.
- class steinerpy.MaxWeightConnectedSubgraph(graph, node_weights, root=None, weight='weight', **kwargs)[source]¶
Bases:
PrizeCollectingProblemMaximum-Weight Connected Subgraph (MWCS).
Finds a connected subgraph maximising the sum of node weights. Nodes with positive weights are modelled as optional terminals with prizes; nodes with negative weights are treated as Steiner points that are included only when they are necessary connectors. A user-supplied (or automatically chosen) root node anchors the solution.
- class steinerpy.BudgetedMaxWeightConnectedSubgraph(graph, node_weights, node_costs, node_budget, root=None, weight='weight', **kwargs)[source]¶
Bases:
MaxWeightConnectedSubgraphMaximum-Weight Connected Subgraph with a vertex-cost Budget (MWCSPB), thesis Ch. 5.6.
Like
MaxWeightConnectedSubgraph, but every chosen vertex consumes a vertex cost and the total cost of the chosen connected subgraph may not exceed anode_budget. Maximises the total node weight subject to that budget.Solved by a rooted directed model with optional per-vertex connectivity and a node-cost budget constraint (the existing
budget=kwarg is an edge-cost budget that maximises connected terminals — a different problem). Supports both the"highs"and"gurobi"backends. This variant does not use the SAP-transform / dual-ascent accelerators (same opt-out convention as the budget/degree variants).- __init__(graph, node_weights, node_costs, node_budget, root=None, weight='weight', **kwargs)[source]¶
- Parameters:
graph (Graph) – networkx undirected graph.
node_weights (Dict) – dict node -> weight (positive or negative).
node_costs (Dict) – dict node -> non-negative vertex cost.
node_budget (float) – maximum total vertex cost of the chosen subgraph.
root – optional root; defaults to the highest-weight node.
weight (str) – edge attribute name (edges are not part of the objective).
- get_solution(time_limit=300, log_file='', solver='highs', threads=None)[source]¶
Solve the prize-collecting problem.
By default uses the penalty/Big-M flow ILP (HiGHS only). Two opt-in fast paths route through the classic PCSTP/MWCSP -> SAP transformation and the existing dual-ascent + directed-cut machinery:
exact=False— heuristic-only mode: return the dual-ascent SAP primal with no ILP; theSolution.gapis a valid optimality gap (0.0certifies provable optimality).pc_transform=True— exact solve via the SAP transformation with dual-ascent lower bound, reduced-cost fixing, cut-seeding, warm-start and proven-optimal early-exit.
Both require the instance to be a classic forgo-prize PCSTP (or an MWCSP); otherwise a clear
NotImplementedErroris raised.solverselects"highs"(default) or"gurobi"for the transform path’s ILP.- Parameters:
- Return type:
Further variants¶
- class steinerpy.PartialTerminalSteinerProblem(graph, terminal_groups, partial_terminals, weight='weight', **kwargs)[source]¶
Bases:
SteinerProblemPartial Terminal Steiner Tree Problem (PTSTP), thesis Ch. 5.1.
A Steiner tree problem with the extra requirement that a designated subset of the terminals — the partial terminals — must be leaves of the solution tree.
Solved by the transformation to a plain Steiner tree problem (thesis Sec. 5.1):
remove every edge whose both endpoints are partial terminals, and
add a large constant
M(the sum of all edge weights) to every edge incident to a partial terminal, so no optimal Steiner tree routes through one.
Each partial terminal is then a leaf in an optimal solution, so exactly one
+Medge is incident to it; the constant is subtracted back out of the reported objective. No partial-terminal Steiner tree need exist — an infeasible instance raisesRuntimeError.Follows the thesis assumption of at least three terminals; with one or two terminals the problem is trivial and the leaf requirement is vacuous.
- __init__(graph, terminal_groups, partial_terminals, weight='weight', **kwargs)[source]¶
Initialize the SteinerProblem (can be tree or forest).
- get_solution(*args, **kwargs)[source]¶
Get the solution of the Steiner Problem.
When
budgetwas supplied at construction time the solver maximises the number of connected terminals subject to the budget constraint and returns aBudgetSolution. Otherwise the solver minimises the total edge cost and returns a plainSolution.Optional modifiers set at construction time (
max_degree,budget) are applied automatically regardless of which problem class is used.- Parameters:
time_limit – time limit in seconds.
log_file – path to the log file.
solver – which MIP solver to use –
"highs"(default) or"gurobi". When"gurobi"is chosen the cut-based formulation is solved with Gurobi lazy-cut callbacks, which requires gurobipy and a valid Gurobi license to be installed.exact – when
True(default) solve to optimality. WhenFalserun heuristic-only mode: return the dual-ascent primal with no ILP — much faster, and the returnedSolution.gapis a valid optimality gap (0.0⇒ provably optimal). Supported for plain Steiner tree/forest and directed problems only; raisesNotImplementedErrorfor budget/degree-constrained variants.
- Returns:
Solution(orBudgetSolutionwhen a budget is set).- Raises:
ValueError – if an unknown solver name is provided.
ImportError – if
solver="gurobi"but gurobipy is not installed.
- Return type:
- class steinerpy.FullTerminalSteinerProblem(graph, terminal_groups, weight='weight', **kwargs)[source]¶
Bases:
PartialTerminalSteinerProblemFull Terminal Steiner Tree Problem (FTSTP), thesis Ch. 5.1 — the special case of
PartialTerminalSteinerProblemin which every terminal must be a leaf.
- class steinerpy.GroupSteinerProblem(graph, groups, weight='weight', **kwargs)[source]¶
Bases:
SteinerProblemGroup Steiner Tree Problem (GSTP), thesis Ch. 5.7.
Given vertex groups, find a minimum-cost tree that contains at least one vertex from each group. Solved by the Voss (1999) transformation to a plain Steiner tree problem: add one artificial super-terminal per group, connected by zero-cost edges to every vertex of that group, then solve the Steiner tree problem whose terminals are the super-terminals. The zero-cost connector edges are stripped from the reported solution (they contribute nothing to the objective).
- __init__(graph, groups, weight='weight', **kwargs)[source]¶
Initialize the SteinerProblem (can be tree or forest).
- get_solution(*args, **kwargs)[source]¶
Get the solution of the Steiner Problem.
When
budgetwas supplied at construction time the solver maximises the number of connected terminals subject to the budget constraint and returns aBudgetSolution. Otherwise the solver minimises the total edge cost and returns a plainSolution.Optional modifiers set at construction time (
max_degree,budget) are applied automatically regardless of which problem class is used.- Parameters:
time_limit – time limit in seconds.
log_file – path to the log file.
solver – which MIP solver to use –
"highs"(default) or"gurobi". When"gurobi"is chosen the cut-based formulation is solved with Gurobi lazy-cut callbacks, which requires gurobipy and a valid Gurobi license to be installed.exact – when
True(default) solve to optimality. WhenFalserun heuristic-only mode: return the dual-ascent primal with no ILP — much faster, and the returnedSolution.gapis a valid optimality gap (0.0⇒ provably optimal). Supported for plain Steiner tree/forest and directed problems only; raisesNotImplementedErrorfor budget/degree-constrained variants.
- Returns:
Solution(orBudgetSolutionwhen a budget is set).- Raises:
ValueError – if an unknown solver name is provided.
ImportError – if
solver="gurobi"but gurobipy is not installed.
- Return type:
- class steinerpy.HopConstrainedSteinerProblem(graph, root, terminals, hop_limit, weight='weight', **kwargs)[source]¶
Bases:
DirectedSteinerProblemHop-Constrained Directed Steiner Tree Problem (HCDSTP), thesis Ch. 5.8.
A Steiner arborescence in which the number of arcs (hops) is bounded by
hop_limitand no terminal (other than the root) has outgoing arcs.Built on
DirectedSteinerProblem: the outgoing arcs of every non-root terminal are removed from the graph, and the directed-cut model adds the hop constraintsum(y1) <= hop_limit(seesteinerpy.mathematical_model.add_hop_constraint()). The dual-ascent accelerator is skipped automatically becausehop_limitis set.
- class steinerpy.RectilinearSteinerProblem(points, weight='weight', **kwargs)[source]¶
Bases:
SteinerProblemRectilinear Steiner Minimum Tree (RSMT), thesis Ch. 5.4.
Given points in the plane, find a minimum-total-length tree that uses only horizontal and vertical segments (the L1 / Manhattan metric), allowing extra Steiner points. Reduced exactly to a Steiner tree problem on the Hanan grid (Hanan 1966); the reported objective is the total rectilinear length. Practical for modest point counts (the grid has up to
k^2nodes forkpoints).Nodes of the underlying graph are
(x, y)coordinate tuples.- Parameters:
weight (str)
- __init__(points, weight='weight', **kwargs)[source]¶
Initialize the SteinerProblem (can be tree or forest).
- Parameters:
graph – networkx graph (Graph or DiGraph).
terminal_groups – nested list of terminals.
weight (str) – edge attribute specified by this string as the edge weight.
- get_solution(*args, **kwargs)[source]¶
Get the solution of the Steiner Problem.
When
budgetwas supplied at construction time the solver maximises the number of connected terminals subject to the budget constraint and returns aBudgetSolution. Otherwise the solver minimises the total edge cost and returns a plainSolution.Optional modifiers set at construction time (
max_degree,budget) are applied automatically regardless of which problem class is used.- Parameters:
time_limit – time limit in seconds.
log_file – path to the log file.
solver – which MIP solver to use –
"highs"(default) or"gurobi". When"gurobi"is chosen the cut-based formulation is solved with Gurobi lazy-cut callbacks, which requires gurobipy and a valid Gurobi license to be installed.exact – when
True(default) solve to optimality. WhenFalserun heuristic-only mode: return the dual-ascent primal with no ILP — much faster, and the returnedSolution.gapis a valid optimality gap (0.0⇒ provably optimal). Supported for plain Steiner tree/forest and directed problems only; raisesNotImplementedErrorfor budget/degree-constrained variants.
- Returns:
Solution(orBudgetSolutionwhen a budget is set).- Raises:
ValueError – if an unknown solver name is provided.
ImportError – if
solver="gurobi"but gurobipy is not installed.
- Return type:
Solutions¶
- class steinerpy.Solution(gap, runtime, objective, selected_edges, original_selected_edges=None, was_preprocessed=False)[source]¶
- Parameters:
- property edges¶
Return the edges in the original graph.
- class steinerpy.PrizeCollectingSolution(selected_nodes=None, penalties=None, total_prize=0, edge_cost=0, **kwargs)[source]¶
Bases:
Solution- property net_value¶
Total prize collected minus edge costs and penalties.
- class steinerpy.NodeWeightedSolution(selected_nodes=None, **kwargs)[source]¶
Bases:
SolutionSolution for the Node-Weighted Steiner Tree Problem.
- Parameters:
selected_nodes (List | None)
Deprecated¶
These classes remain for backward compatibility; pass max_degree= or
budget= to the base problem class instead (see Problem variants).
- class steinerpy.DegreeConstrainedSteinerProblem(graph, terminal_groups, max_degree, **kwargs)[source]¶
Bases:
SteinerProblemDegree-Constrained Steiner Tree Problem.
Deprecated since version 0.2.0: Pass
max_degreedirectly toSteinerProblem(or any other problem class) instead::SteinerProblem(graph, terminal_groups, max_degree=2)
This class is kept for backward compatibility and will be removed in a future version.
- class steinerpy.BudgetConstrainedSteinerProblem(graph, terminal_groups, budget, **kwargs)[source]¶
Bases:
SteinerProblemBudget-Constrained Steiner Tree Problem.
Deprecated since version 0.2.0: Pass
budgetdirectly toSteinerProblem(or any other problem class) instead::SteinerProblem(graph, terminal_groups, budget=10.0)
This class is kept for backward compatibility and will be removed in a future version.