Skip to content

[PEP] NetworkX Graph Representation of Pyomo Model #2272

Open
@michaelbynum

Description

@michaelbynum

Summary

There are many applications in which it is useful to generate a graph representation of a pyomo model using NetworkX. It would be nice to have a single common function/module in Pyomo with this functionality that can be utilized for multiple applications. However, the desired API is not clear. The purpose of this PEP is to discuss the API.

The problem

The problem is that nodes in a NetworkX graph must be hashable. This motivates two potential solutions:

  1. Create hashable wrappers for Pyomo components and use them as nodes in the NetworkX graph.
  2. Use a hashable object (probably an int) as nodes and provide some sort of mapping from nodes to Pyomo components.

Solution 1: Hashable Wrappers

What it looks like

class HashableWrapper(object):
    def __init__(self, comp):
        self.comp = comp

    def __eq__(self, other):
        if type(other) is _CompNode:
            return self.comp is other.comp
        return False

    def __hash__(self):
        return hash(id(self.comp))


def graph_from_pyomo(m: _BlockData,
                     include_objective: bool = True,
                     active: bool = True) -> nx.Graph:
    graph = nx.Graph()

    for v in ComponentSet(m.component_data_objects(pe.Var, descend_into=True)):
        graph.add_node(HashableWrapper(v))

    for con in OrderedSet(m.component_data_objects(pe.Constraint, descend_into=True, active=active)):
        graph.add_node(HashableWrapper(con)) # This does not need the wrapper, but consistency is nice
        for v in identify_variables(con.body, include_fixed=True):
            graph.add_edge(HashableWrapper(con), HashableWrapper(v))

    if include_objective:
        for obj in ComponentSet(m.component_data_objects(pe.Objective, descend_into=True, active=active)):
            graph.add_node(HashableWrapper(obj))
            for v in identify_variables(obj.expr, include_fixed=True):
                graph.add_edge(HashableWrapper(obj), HashableWrapper(v))

    return graph

Example Usage

g = graph_from_pyomo(m)
for n1, n2 in graph.edges():
    n1.comp.pprint()

for v in m.component_data_objects(pe.Var):
    g.neighbors(HashableWrapper(v))

Pros

  • Easy to use
  • Only one object to worry about

Cons

  • Needs a new object for every Pyomo component

Solution 2: Provide a mapping from nodes to Pyomo components

What it looks like

def graph_from_pyomo(m: _BlockData, include_objective: bool = True, active: bool = True) -> nx.Graph:
    graph = nx.Graph()

    ndx = 0
    comp_map = dict()
    rev_comp_map = ComponentMap()

    for v in ComponentSet(m.component_data_objects(pe.Var, descend_into=True)):
        graph.add_node(ndx)
        comp_map[ndx] = v
        rev_comp_map[v] = ndx
        ndx += 1

    for con in OrderedSet(m.component_data_objects(pe.Constraint, descend_into=True, active=active)):
        graph.add_node(ndx)
        comp_map[ndx] = con
        rev_comp_map[con] = ndx
        ndx += 1
        for v in identify_variables(con.body, include_fixed=True):
            graph.add_edge(rev_comp_map[con], rev_comp_map[v])

    if include_objective:
        for obj in ComponentSet(m.component_data_objects(pe.Objective, descend_into=True, active=active)):
            graph.add_node(ndx)
            comp_map[ndx] = obj
            rev_comp_map[obj] = ndx
            ndx += 1
            for v in identify_variables(obj.expr, include_fixed=True):
                graph.add_edge(rev_comp_map[obj], rev_comp_map[v])

    return graph, comp_map, rev_comp_map

Example Usage

g, cm, rcm = graph_from_pyomo(m)
for n1, n2 in graph.edges():
    cm[n1].pprint()

for v in m.component_data_objects(pe.Var):
    g.neighbors(rcm[v])

Pros

  • Very simple (I like simple)
  • Lightweight solution

Cons

  • Working with the resulting graph is slightly more cumbersome

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions