Source code for idaes.ui.model_browser

##############################################################################
# Institute for the Design of Advanced Energy Systems Process Systems
# Engineering Framework (IDAES PSE Framework) Copyright (c) 2018, by the
# software owners: The Regents of the University of California, through
# Lawrence Berkeley National Laboratory,  National Technology & Engineering
# Solutions of Sandia, LLC, Carnegie Mellon University, West Virginia
# University Research Corporation, et al. All rights reserved.
#
# Please see the files COPYRIGHT.txt and LICENSE.txt for full copyright and
# license information, respectively. Both files are also available online
# at the URL "https://github.com/IDAES/idaes".
##############################################################################
"""
A simple GUI viewer for Pyomo models.
"""
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import  # disable implicit relative imports

__author__ = "John Eslick"

import os
import warnings
import logging
import re

try:
    from PyQt5 import QtCore
except:
    logging.exception("Cannot import PyQt5.QtCore")
    try:
        from PyQt4 import QtCore
    except:
        logging.exception("Cannot import PyQt4.QtCore")
    else:
        try:
            from PyQt4.QtGui import QAbstractItemView
            from PyQt4.QtCore import QAbstractItemModel
            from PyQt4 import uic
        except:
            logging.exception("Cannot import PyQt4")
else:
    try:
        from PyQt5.QtWidgets import QAbstractItemView
        from PyQt5.QtCore import QAbstractItemModel
        from PyQt5 import uic
    except:
        logging.exception("Cannot import PyQt5")

from idaes.ui.util import insert_jupyter_code
from pyomo.core.base.block import _BlockData
from pyomo.core.base.var import _VarData
from pyomo.core.base.constraint import _ConstraintData
from pyomo.core.base.expression import _ExpressionData
from pyomo.network.port import SimplePort
from pyomo.core.base.param import _ParamData
from pyomo.environ import Block, Var, Constraint, Param, Expression, value

mypath = os.path.dirname(__file__)

try:
    _ModelBrowserUI, _ModelBrowser = \
        uic.loadUiType(os.path.join(mypath, "model_browser.ui"))
except:
    # This lets the file still be imported, but you won't be able to use it
    class _ModelBrowserUI(object):
        pass
    class _ModelBrowser(object):
        pass

def _o_code_name(o):
    if "[" in o.name:
        name_list = []
        b = o
        while b.parent_block() is not None: #dosen't include main model block
            name_list.append(b.getname().split("[")[0])
            if b.index() is not None:
                name_list[-1] = "{}[{}]".format(name_list[-1], repr(b.index()))
            b = b.parent_block()
        return ".".join(reversed(name_list))
    else:
        return o.name

[docs]class ModelBrowser(_ModelBrowser, _ModelBrowserUI): def __init__(self, ui_setup, parent=None, standard="Var"): """ Create a dock widdget with a QTreeView of a Pyomo model. Args: parent: parent widget ui_setup: Contains model and model_str: Pyomo model and str for code standard: A standard setup for differnt types of model components {"Var", "Constraint", "Param", "Expression"} """ super(ModelBrowser, self).__init__(parent=parent) self.setupUi(self) self.ui_setup = ui_setup self.ui_setup.updated.connect(self.update_model) if standard == "Var": # This if block sets up standard views components = Var columns = ["name", "value", "ub", "lb", "fixed", "stale"] editable = ["value", "ub", "lb", "fixed"] self.setWindowTitle("Variables") elif standard == "Constraint": components = Constraint columns = ["name", "value", "ub", "lb", "active", "expr"] editable = ["active"] self.setWindowTitle("Constraints") elif standard == "Param": components = Param columns = ["name", "value", "_mutable"] editable = ["value"] self.setWindowTitle("Parameters") elif standard == "Expression": components = Expression columns = ["name", "value"] editable = [] self.setWindowTitle("Expressions") else: raise Exception("Not a valid view type") # Create a data model. This is what translates the Pyomo model into # a tree view. datmodel = ComponentDataModel(self, ui_setup=ui_setup, columns=columns, components=components, editable=editable) self.datmodel = datmodel self.treeView.setModel(datmodel) self.treeView.setColumnWidth(0,400) # Set selection behavior so you select a whole row, and can selection # multiple rows. self.treeView.setSelectionBehavior(QAbstractItemView.SelectRows) self.treeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
[docs] def refresh(self): added = self.datmodel._update_tree() self.datmodel.layoutChanged.emit()
[docs] def toggle(self): if self.isVisible(): self.hide() else: self.show()
[docs] def update_model(self): self.datmodel.update_model()
[docs] def calculate_all(self): for i in self.datmodel.rootItems: i.calculate_children()
[docs]class ComponentDataItem(object): """ This is a container for a Pyomo component to be displayed in a model tree view. """ def __init__(self, parent, o, ui_setup): self.ui_setup = ui_setup self.data = o self.parent = parent self.children = [] self.ids = {} self.clear_cache() self.get_callback = { "value": self._get_value_callback, "lb": self._get_lb_callback, "ub": self._get_ub_callback, "expr": self._get_expr_callback} self.set_callback = { "value":self._set_value_callback, "lb":self._set_lb_callback, "ub":self._set_ub_callback, "active":self._set_active_callback, "fixed":self._set_fixed_callback}
[docs] def data_items(): for i in self.children: i.data_items() yield self
[docs] def add_child(self, o): item = ComponentDataItem(self, o, ui_setup=self.ui_setup) self.children.append(item) self.ids[id(o)] = item return item
[docs] def clear_cache(self): self._cache_value = None self._cache_lb = None self._cache_ub = None
[docs] def calculate_children(self): self.calculate() for i in self.children: i.calculate_children()
[docs] def calculate(self): if isinstance(self.data, _ExpressionData): #try: self._cache_value = value(self.data) #except: # pass if isinstance(self.data, _ConstraintData) and self.data.active: try: self._cache_value = value(self.data.body) self._cache_lb = value(self.data.lower) self._cache_ub = value(self.data.upper) except: pass
[docs] def get(self, a): if a in self.get_callback: return self.get_callback[a]() else: try: return getattr(self.data, a) except: return None
[docs] def set(self, a, val): if a in self.set_callback: return self.set_callback[a](val) else: try: return setattr(self.data, a, val) except: return None
def _get_expr_callback(self): if hasattr(self.data, "expr"): return str(self.data.expr) else: return None def _get_value_callback(self): if isinstance(self.data, (_VarData, _ParamData, float, int)): try: return value(self.data) except: return None else: return self._cache_value def _get_lb_callback(self): if isinstance(self.data, _VarData): return self.data.lb else: return self._cache_lb def _get_ub_callback(self): if isinstance(self.data, _VarData): return self.data.ub else: return self._cache_ub def _set_value_callback(self, val): if isinstance(self.data, _VarData): try: self.data.value = float(val) except: return c = "{}.{}.value = {}".format( self.ui_setup.model_str, _o_code_name(self.data), float(val)) insert_jupyter_code(c) elif isinstance(self.data, _ParamData): if not self.data._mutable: return try: self.data.value = float(val) except: return c = "{}.{}.value = {}".format(self.ui_setup.model_str, _o_code_name(self.data), float(val)) insert_jupyter_code(c) def _set_lb_callback(self, val): if isinstance(self.data, _VarData): try: self.data.setlb(float(val)) except: return c = "{}.{}.setlb({})".format(self.ui_setup.model_str, _o_code_name(self.data), float(val)) insert_jupyter_code(c) def _set_ub_callback(self, val): if isinstance(self.data, _VarData): try: self.data.setub(float(val)) except: return c = "{}.{}.setub({})".format(self.ui_setup.model_str, _o_code_name(self.data), float(val)) insert_jupyter_code(c) def _set_active_callback(self, val): if val == "True" or val == "true" or val == "1": val = True else: val = False try: if val: self.data.activate() else: self.data.deactivate() except: return if val: c = "{}.{}.activate()".format(self.ui_setup.model_str, _o_code_name(self.data)) else: c = "{}.{}.deactivate()".format(self.ui_setup.model_str, _o_code_name(self.data)) insert_jupyter_code(c) def _set_fixed_callback(self, val): if val == "True" or val == "true" or val == "1": val = True else: val = False try: if val: self.data.fix() else: self.data.unfix() except: return if val: c = "{}.{}.fix()".format(self.ui_setup.model_str, _o_code_name(self.data)) else: c = "{}.{}.unfix()".format(self.ui_setup.model_str, _o_code_name(self.data)) insert_jupyter_code(c)
[docs]class ComponentDataModel(QAbstractItemModel): """ This is a data model to provide the tree structure and information to the tree viewer """ def __init__(self, parent, ui_setup, columns=["name", "value"], components=(Var,), editable=[]): super(ComponentDataModel, self).__init__(parent) self.column = columns self._col_editable = editable self.ui_setup = ui_setup self.components = components self.update_model()
[docs] def update_model(self): self.rootItems = [] self._create_tree(o=self.ui_setup.model)
def _update_tree(self, parent=None, o=None): """ Check tree structure against the Pyomo model to add or delete components as needed. The arguments are to be used in the recursive function. Entering into this don't specify any args. """ # Blocks are special they define the hiarchy of the model, so first # check for blocks. Other comonent can be handeled togeter if o is None and len(self.rootItems) > 0: #top level object (no parent) parent = self.rootItems[0] # should be single root node for now o = parent.data # start with root node for no in o.component_objects(descend_into=False): # This will traverse the whole Pyomo model tree self._update_tree(parent=parent, o=no) return elif o is None: # if o is None, but no root nodes (when no model) return # past the root node go down here item = parent.ids.get(id(o), None) if item is not None: # check if any children of item where deleted for i in item.children: try: if i.data.parent_block() is None: i.parent.children.remove(i) del(i.parent.ids[id(i.data)]) del(i) # probably should descend down and delete stuff except AttributeError: # Probably an element of an indexed immutable param pass if isinstance(o, _BlockData): #single block or element of indexed block if item is None: item = self._add_item(parent=parent, o=o) for no in o.component_objects(descend_into=False): self._update_tree(parent=item, o=no) elif isinstance(o, Block): #indexed block, so need to add elements if item is None: item = self._add_item(parent=parent, o=o) for key in sorted(o.keys()): self._update_tree(parent=item, o=o[key]) elif isinstance(o, self.components): #anything else if item is None: item = self._add_item(parent=parent, o=o) for key in sorted(o.keys()): if key == None: break # Single variable so skip item2 = item.ids.get(id(o[key]), None) if item2 is None: item2 = self._add_item(parent=item, o=o[key]) item2._visited = True return def _create_tree(self, parent=None, o=None): """ This create a model tree structure to display in a tree view. Args: parent: a ComponentDataItem underwhich to create a TreeItem o: A Pyomo component to add to the tree """ # Blocks are special they define the hiarchy of the model, so first # check for blocks. Other comonent can be handeled togeter if isinstance(o, _BlockData): #single block or element of indexed block item = self._add_item(parent=parent, o=o) for no in o.component_objects(descend_into=False): self._create_tree(parent=item, o=no) elif isinstance(o, Block): #indexed block, so need to add elements item = self._add_item(parent=parent, o=o) for key in sorted(o.keys()): self._create_tree(parent=item, o=o[key]) elif isinstance(o, self.components): #anything else item = self._add_item(parent=parent, o=o) for key in sorted(o.keys()): if key == None: break #Single variable so skip self._add_item(parent=item, o=o[key]) def _add_item(self, parent, o): """ Add a root item if parent is None, otherwise add a child """ if parent is None: item = self._add_root_item(o) else: item = parent.add_child(o) return item def _add_root_item(self, o): """ Add a root tree item """ item = ComponentDataItem(None, o, ui_setup=self.ui_setup) self.rootItems.append(item) return item
[docs] def parent(self, index): if not index.isValid(): return QtCore.QModelIndex() item = index.internalPointer() if item.parent is None: return QtCore.QModelIndex() else: return self.createIndex(0, 0, item.parent)
[docs] def index(self, row, column, parent=QtCore.QModelIndex()): if not parent.isValid(): return self.createIndex(row, column, self.rootItems[row]) parentItem = parent.internalPointer() return self.createIndex(row, column, parentItem.children[row])
[docs] def columnCount(self, parent=QtCore.QModelIndex()): """ Return the number of columns """ return len(self.column)
[docs] def rowCount(self, parent=QtCore.QModelIndex()): if not parent.isValid(): return len(self.rootItems) return len(parent.internalPointer().children)
[docs] def data(self, index=QtCore.QModelIndex(), role=QtCore.Qt.DisplayRole): if role==QtCore.Qt.DisplayRole or role==QtCore.Qt.EditRole: a = self.column[index.column()] return index.internalPointer().get(a) elif role==QtCore.Qt.ToolTipRole: if self.column[index.column()] == "name": o = index.internalPointer() if isinstance(o.data, _ConstraintData): return o.get("expr") else: return o.get("doc") else: return
[docs] def setData(self, index, value, role=QtCore.Qt.EditRole): if role==QtCore.Qt.EditRole: a = self.column[index.column()] if a in self._col_editable: index.internalPointer().set(a, value) return 1
[docs] def headerData(self, i, orientation, role=QtCore.Qt.DisplayRole): """ Return the column headings for the horizontal header and index numbers for the vertical header. """ if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole: return self.column[i] return None
[docs] def flags(self, index=QtCore.QModelIndex()): if self.column[index.column()] in self._col_editable: return(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable) else: return(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)