##############################################################################
# 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"]
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_cb = {
"value": self._get_value_cb,
"lb": self._get_lb_cb,
"ub": self._get_ub_cb}
self.set_cb = {
"value":self._set_value_cb,
"lb":self._set_lb_cb,
"ub":self._set_ub_cb,
"active":self._set_active_cb,
"fixed":self._set_fixed_cb}
[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_cb:
return self.get_cb[a]()
else:
try:
return getattr(self.data, a)
except:
return None
[docs] def set(self, a, val):
if a in self.set_cb:
return self.set_cb[a](val)
else:
try:
return setattr(self.data, a, val)
except:
return None
def _get_value_cb(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_cb(self):
if isinstance(self.data, _VarData):
return self.data.lb
else:
return self._cache_lb
def _get_ub_cb(self):
if isinstance(self.data, _VarData):
return self.data.ub
else:
return self._cache_ub
def _set_value_cb(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_cb(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_cb(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_cb(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_cb(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":
return index.internalPointer().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 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)