mis.pipeline.kernelization
source module mis.pipeline.kernelization
Classes
-
BaseKernelization — Shared base class for kernelization.
-
UnweightedKernelization — Apply well-known transformations to the graph to reduce its size without compromising the result.
-
BaseRebuilder — The pre-processing operations attempt to remove edges and/or vertices from the original graph. Therefore, when we build a MIS for these reduced graphs (the "partial solution"), we may end up with a solution that does not work for the original graph.
source class Kernelization(config: SolverConfig, graph: nx.Graph)
Bases : BasePreprocessor
Methods
-
preprocess — Run preprocessing steps on the graph.
-
rebuild — Expand from MIS solutions on a reduced graph obtained by
preprocess()
into solutions on the original graph. -
is_independent — Determine if a set of nodes represents an independent set within a given graph.
source method Kernelization.preprocess() → nx.Graph
Run preprocessing steps on the graph.
This will reduce the size of the graph. Do not forget to call rebuild()
to
convert solutions on the reduced graph into solutions on the original graph!
source method Kernelization.rebuild(partial_solution: set[int]) → set[int]
Expand from MIS solutions on a reduced graph obtained by preprocess()
into
solutions on the original graph.
Parameters
-
partial_solution A solution on the reduced graph. Note that we do not — check that this solution is correct.
-
Returns — A solution on the original graph.
source method Kernelization.is_independent(nodes: list[int]) → bool
Determine if a set of nodes represents an independent set within a given graph.
Parameters
-
A list of nodes within the latest iteration of the graph.
source class BaseKernelization(config: SolverConfig, graph: nx.Graph)
Bases : BasePreprocessor, abc.ABC
Shared base class for kernelization.
Methods
-
rebuild — Rebuild a MIS solution to the original graph from a partial MIS solution on the reduced graph obtained by kernelization.
-
is_independent — Determine if a set of nodes represents an independent set within a given graph.
-
is_subclique — Determine whether a list of nodes represents a clique within the graph, i.e. whether every pair of nodes is connected.
-
node_weight — Return the weight of a single node.
-
subgraph_weight — Return the total weight of a subgraph.
-
is_maximum — Determine whether any neighbor of a node has a weight strictly greater than that node.
-
is_isolated_and_maximum — Determine whether a node is isolated and maximum, i.e. 1. this node + its neighbors represent a clique; AND 2. no node in the neighborhood has a weight strictly greater than
node
. -
add_node — Add a new node with a unique index.
-
preprocess — Apply all rules, exhaustively, until the graph cannot be reduced further, storing the rules for rebuilding after the fact.
-
add_rebuilder — Store a rebuilder step to be called during rebuild().
-
initial_cleanup — One-time cleanup of nodes that are trivially useless, e.g. negative weights.
-
search_rule_neighborhood_removal — Weighted: If a node has a greater weight than all its neighbors together, remove the node (it will be part of the WMIS) and all its neighbors (they won't). Unweighted: Noop.
-
get_nodes_with_strictly_higher_weight — Return the nodes with a weight strictly higher than a give node.
-
apply_rule_isolated_node_removal — Remove an isolated node / store the rebuild operation.
-
search_rule_isolated_node_removal — Remove any isolated node (see
is_isolated
for a definition). -
fold_three — Fold three nodes V, U and X into a new single node V'.
-
apply_rule_node_fold — Fold three nodes V, U and X into a new single node / store the rebuild operation.
-
search_rule_node_fold — If a node V has exactly two neighbors U and X and there is no edge between U and X, fold U, V and X and into a single node.
-
twin_category — Determine which operations we can perform on two twin nodes.
-
find_removable_twin — Find a twin of a node, i.e. another node with the same neighbors, and check what we can do with this node.
-
fold_twin — Fold two twins U and V into a single node V'.
-
apply_rule_twins_in_solution — Remove two twin nodes and their neighbors / store the rebuild operation.
-
apply_rule_twin_independent — Remove two twin nodes and their neighbors / store the rebuild operation.
-
search_rule_twin_reduction — If a node V and a node U have the exact same neighbors (which indicates that they're not nightbours themselves), we may be able to merge U, V and their neighborhoods into a single node.
-
search_rule_unconfined_and_diamond — Look for unconfined nodes, i.e. a category of nodes for which we can prove easily that they cannot be part of a solution.
source method BaseKernelization.rebuild(partial_solution: set[int]) → set[int]
Rebuild a MIS solution to the original graph from a partial MIS solution on the reduced graph obtained by kernelization.
source method BaseKernelization.is_independent(nodes: list[int]) → bool
Determine if a set of nodes represents an independent set within a given graph.
Returns
-
bool — True if the nodes in
nodes
represent an independent set withingraph
. False otherwise, i.e. if there's at least one connection between two nodes ofnodes
source method BaseKernelization.is_subclique(nodes: list[int]) → bool
Determine whether a list of nodes represents a clique within the graph, i.e. whether every pair of nodes is connected.
source method BaseKernelization.node_weight(node: int) → float
Return the weight of a single node.
source method BaseKernelization.subgraph_weight(nodes: Iterable[int]) → float
Return the total weight of a subgraph.
source method BaseKernelization.is_maximum(node: int, neighbors: list[int]) → bool
Determine whether any neighbor of a node has a weight strictly greater than that node.
source method BaseKernelization.is_isolated_and_maximum(node: int) → bool
Determine whether a node is isolated and maximum, i.e.
1. this node + its neighbors represent a clique; AND
2. no node in the neighborhood has a weight strictly greater than node
.
source method BaseKernelization.add_node(weight: float) → int
Add a new node with a unique index.
source method BaseKernelization.preprocess() → nx.Graph
Apply all rules, exhaustively, until the graph cannot be reduced further, storing the rules for rebuilding after the fact.
source method BaseKernelization.add_rebuilder(rebuilder: BaseRebuilder) → None
Store a rebuilder step to be called during rebuild().
source method BaseKernelization.initial_cleanup() → None
One-time cleanup of nodes that are trivially useless, e.g. negative weights.
source method BaseKernelization.search_rule_neighborhood_removal() → None
Weighted: If a node has a greater weight than all its neighbors together, remove the node (it will be part of the WMIS) and all its neighbors (they won't). Unweighted: Noop.
source method BaseKernelization.get_nodes_with_strictly_higher_weight(node: int, neighborhood: Iterable[int]) → list[int]
Return the nodes with a weight strictly higher than a give node.
Parameters
-
node : int — The main node.
-
neighborhood : Iterable[int] — The list of nodes in which to search for a weight strictly higher than
node
.
Returns
-
list[int] — A list (possibly empty) of nodes from
neighborhood
. All these nodes are guaranteed to have a weight strictly higher than that ofnode
.In unweighted mode, this list is always empty.
source method BaseKernelization.apply_rule_isolated_node_removal(isolated: int) → None
Remove an isolated node / store the rebuild operation.
Parameters
-
isolated An isolated node. We do not re-check that it is isolated.
source method BaseKernelization.search_rule_isolated_node_removal() → None
Remove any isolated node (see is_isolated
for a definition).
source method BaseKernelization.fold_three(v: int, u: int, x: int, v_prime: int) → None
Fold three nodes V, U and X into a new single node V'.
source method BaseKernelization.apply_rule_node_fold(v: Any, w_v: float, u: Any, w_u: float, x: Any, w_x: float) → None
Fold three nodes V, U and X into a new single node / store the rebuild operation.
Parameters
-
v, u, x — Three nodes. U and X MUST both be neightours of V. There MUST NOT be any edge between U and X.
-
w_v, w_u, w_x — The weight of nodes v, u, x. We MUST have w_u + w_x > w_v (always true in unweighted mode). We MUST have w_x <= w_v and w_u <= w_v (always true in unweighted mode).
source method BaseKernelization.search_rule_node_fold() → None
If a node V has exactly two neighbors U and X and there is no edge between U and X, fold U, V and X and into a single node.
source method BaseKernelization.twin_category(u: int, v: int, neighbors: list[int]) → _TwinCategory
Determine which operations we can perform on two twin nodes.
Parameters
-
- u, v — two distinct nodes with the same set of neighbors
-
- neighbors — the neighbors of u (or equivalently v)
source method BaseKernelization.find_removable_twin(v: int) → _Twin | None
Find a twin of a node, i.e. another node with the same neighbors, and check what we can do with this node.
Parameters
-
v : int — a node
-
Returns — A
_Twin
if any twin of v was found and it can be removed,None
otherwise.
source method BaseKernelization.fold_twin(u: int, v: int, v_prime: int, u_neighbors: list[int]) → None
Fold two twins U and V into a single node V'.
Parameters
-
u, v — The nodes to fold.
-
v_prime : int — The new node, already created.
-
u_neighbors : list[int] — The neighbors of U (or equivalently of V).
source method BaseKernelization.apply_rule_twins_in_solution(v: int, u: int, neighbors_u: list[int]) → None
Remove two twin nodes and their neighbors / store the rebuild operation.
We use this rule when we know that the twins will always be part of the solution.
Parameters
-
u, v — The twin nodes.
-
neighbors_u : list[int] — The neighbors of U (or equivalently of V).
source method BaseKernelization.apply_rule_twin_independent(u: int, v: int, neighbors: list[int]) → None
Remove two twin nodes and their neighbors / store the rebuild operation.
We use this rule when U and V are independent, i.e. either all the neighbors are part of the solution or both U and V are part of the solution, but we don't yet know which.
Parameters
-
u, v — The twin nodes.
-
neighbors_u — The neighbors of U (or equivalently of V).
source method BaseKernelization.search_rule_twin_reduction() → None
If a node V and a node U have the exact same neighbors (which indicates that they're not nightbours themselves), we may be able to merge U, V and their neighborhoods into a single node.
Note: as of this writing, in unweighted mode, the heuristic works when there are exactly 3 neighbors.
source method BaseKernelization.search_rule_unconfined_and_diamond() → None
Look for unconfined nodes, i.e. a category of nodes for which we can prove easily that they cannot be part of a solution.
source class UnweightedKernelization(config: SolverConfig, graph: nx.Graph)
Bases : BaseKernelization
Apply well-known transformations to the graph to reduce its size without compromising the result.
This algorithm is adapted from e.g.
https://schulzchristian.github.io/thesis/masterarbeit_demian_hespe.pdf
Methods
-
add_node — Add a node with a weight of exactly 1.0.
-
is_maximum — Since all nodes have the same weight, no node has a strictly higher weight.
-
initial_cleanup — One-time cleanup of nodes that are trivially useless, e.g. negative weights.
-
get_nodes_with_strictly_higher_weight — Since all nodes have the same weight, no node has a strictly higher weight.
-
twin_category — Determine which operations we can perform on two twin nodes.
-
unconfined_loop — Starting from a node V, attempt to determine whether it is unconfined.
-
search_rule_unconfined_and_diamond — Look for unconfined nodes, i.e. nodes for which we can prove easily that they cannot be part of a solution.
source method UnweightedKernelization.add_node(weight: float) → int
Add a node with a weight of exactly 1.0.
Parameters
-
weight : float — MUST be 1.0 in unweighted mode.
Returns
-
int — The index of the new node.
source method UnweightedKernelization.is_maximum(node: int, neighbors: list[int]) → bool
Since all nodes have the same weight, no node has a strictly higher weight.
source method UnweightedKernelization.initial_cleanup() → None
One-time cleanup of nodes that are trivially useless, e.g. negative weights.
source method UnweightedKernelization.get_nodes_with_strictly_higher_weight(node: int, neighborhood: Iterable[int]) → list[int]
Since all nodes have the same weight, no node has a strictly higher weight.
source method UnweightedKernelization.search_rule_neighborhood_removal() → None
source method UnweightedKernelization.twin_category(u: int, v: int, neighbors: list[int]) → _TwinCategory
Determine which operations we can perform on two twin nodes.
Parameters
-
- u, v — two distinct nodes with the same set of neighbors
-
- neighbors — the neighbors of u (or equivalently v)
Returns
-
_TwinCategory — - CannotRemove if the number of neighbors is not exactly 3. - Independent if the neighbors are independent from each other. - InSolution otherwise.
source method UnweightedKernelization.aux_search_confinement(neighbors_S: set[int], S: set[int]) → _ConfinementAux | None
Attempt to find a neighbor U of S such that
- there is exactly one node in S that is a neighbor of U;
- the number of neighbors of U that are not neighbors of S is minimized.
source method UnweightedKernelization.apply_rule_unconfined(v: int) → None
source method UnweightedKernelization.unconfined_loop(v: int, S: set[int], neighbors_S: set[int]) → bool
Starting from a node V, attempt to determine whether it is unconfined.
Parameters
-
v The node we're examining.
-
S (inout) A set we're building to help determine whether v is unconfined. — Should initially be {v}, grown during each iteration.
-
neighbors_S (inout) The set of neighbors of all elements of S
Returns False if the work is over (we couldn't find any matching value).
source method UnweightedKernelization.search_rule_unconfined_and_diamond() → None
Look for unconfined nodes, i.e. nodes for which we can prove easily that they cannot be part of a solution.
source class WeightedKernelization(config: SolverConfig, graph: nx.Graph)
Bases : BaseKernelization
Methods
-
add_node — Add a new node with a unique index.
-
is_maximum — Determine whether any neighbor of a node has a weight strictly greater than that node.
-
initial_cleanup — One-time cleanup of nodes that are trivially useless, e.g. negative weights.
-
get_nodes_with_strictly_higher_weight — Return the nodes with a weight strictly higher than a give node.
-
search_rule_unconfined_and_diamond — Look for unconfined nodes, i.e. a category of nodes for which we can prove easily that they cannot be part of a solution.
-
neighborhood_weight — Return the total weight of the neighbors of a node.
-
apply_rule_neighborhood_removal — Remove a node and its neighbors / store rebuild role.
-
search_rule_neighborhood_removal — If a node has a greater weight than all its neighbors together, remove the node and all its neighbors.
-
twin_category — Determine which operations we can perform on two twin nodes.
source method WeightedKernelization.add_node(weight: float) → int
Add a new node with a unique index.
Parameters
-
weight : float — A strictly positive weight.
-
Returns — The index of the new node.
source method WeightedKernelization.is_maximum(node: int, neighbors: list[int]) → bool
Determine whether any neighbor of a node has a weight strictly greater than that node.
source method WeightedKernelization.initial_cleanup() → None
One-time cleanup of nodes that are trivially useless, e.g. negative weights.
source method WeightedKernelization.get_nodes_with_strictly_higher_weight(node: int, neighborhood: Iterable[int]) → list[int]
Return the nodes with a weight strictly higher than a give node.
Parameters
-
node : int — The main node.
-
neighborhood : Iterable[int] — The list of nodes in which to search for a weight strictly higher than
node
.
Returns
-
list[int] — A list (possibly empty) of nodes from
neighborhood
. All these nodes are guaranteed to have a weight strictly higher than that ofnode
.
source method WeightedKernelization.search_rule_unconfined_and_diamond() → None
Look for unconfined nodes, i.e. a category of nodes for which we can prove easily that they cannot be part of a solution.
As of this writing, we don't know how to detect unconfined nodes in weighted mode, so this step is a NOOP.
source method WeightedKernelization.neighborhood_weight(node: int) → float
Return the total weight of the neighbors of a node.
source method WeightedKernelization.apply_rule_neighborhood_removal(node: int) → None
Remove a node and its neighbors / store rebuild role.
source method WeightedKernelization.search_rule_neighborhood_removal() → None
If a node has a greater weight than all its neighbors together, remove the node and all its neighbors.
During rebuild, the node will always be part of the WMIS, the neighbors never will.
source method WeightedKernelization.twin_category(u: int, v: int, neighbors: list[int]) → _TwinCategory
Determine which operations we can perform on two twin nodes.
Parameters
-
- u, v — two distinct nodes with the same set of neighbors
-
- neighbors — the neighbors of u (or equivalently v)
Returns
-
_TwinCategory — If the total weight of U + V is at least as large of the total weight of the neighbors, we can always find a solution in which U and V both are, so
InSolution
. If the neighbors are independent and, naming X the neighbor with the smallest weight, if the weight of U + V + X is at least as large of the total weight of the neighbors, then there is always a solution in which either both U and V are, or all the neighbors are, soIndependent
. Otherwise, we don't have any specific preprocessing for these twins, soCannotRemove
.
source class BaseRebuilder()
Bases : abc.ABC
The pre-processing operations attempt to remove edges and/or vertices from the original graph. Therefore, when we build a MIS for these reduced graphs (the "partial solution"), we may end up with a solution that does not work for the original graph.
Each rebuilder corresponds to one of the operations that previously reduced the size of the graph, and is charged with adapting the MIS solution to the greater graph.
Methods
source method BaseRebuilder.rebuild(partial_solution: set[int]) → None
source class RebuilderIsolatedNodeRemoval(isolated: int, higher: list[int])
source method RebuilderIsolatedNodeRemoval.rebuild(partial_solution: set[int]) → None
source class RebuilderNodeFolding(v: int, u: int, x: int, v_prime: int)
source method RebuilderNodeFolding.rebuild(partial_solution: set[int]) → None
source class RebuilderUnconfined()
Bases : BaseRebuilder
Methods
-
rebuild — By definition, unconfined nodes are never part of the solution, so rebuilding is a noop.
source method RebuilderUnconfined.rebuild(partial_solution: set[int]) → None
By definition, unconfined nodes are never part of the solution, so rebuilding is a noop.
source class RebuilderTwinIndependent(v: int, u: int, neighbors: list[int], v_prime: int)
Bases : BaseRebuilder
Invariants
- V has exactly the same neighbors as U;
- there is no self-loop around U or V (hence U and V are not neighbors);
- there is no edge between any of the neighbors;
- V' is the node obtained by merging U, V and the neighbors.
Methods
source method RebuilderTwinIndependent.rebuild(partial_solution: set[int]) → None
source class RebuilderTwinAlwaysInSolution(v: int, u: int)
Bases : BaseRebuilder
Invariants
- V has exactly the same neighbors as U;
- there is no self-loop around U;
- there is at least one connection between two neighbors of U.
Methods
source method RebuilderTwinAlwaysInSolution.rebuild(partial_solution: set[int]) → None
source class RebuilderNeighborhoodRemoval(dominant_vertex: int)
source method RebuilderNeighborhoodRemoval.rebuild(partial_solution: set[int]) → None