Source code for idaes.ui.ui

##############################################################################
# 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"
__version__ = "1.0.0"

import time
import os
import warnings
import logging
import threading
import datetime
import jupyter_client
import json
from IPython import get_ipython
import idaes.ui.report as rpt

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 QFileDialog, QMessageBox
            from PyQt4 import uic
        except:
            logging.exception("Cannot import PyQt4")
else:
    try:
        from PyQt5.QtWidgets import QFileDialog, QMessageBox
        from PyQt5 import uic
    except:
        logging.exception("Cannot import PyQt5")

from idaes.ui.model_browser import ModelBrowser
from idaes.ui.flowsheet import DrawFlowsheet
from idaes.ui.util import insert_jupyter_code, \
    toggle_jupyter_header, close_page, save_notebook, show_jupyter_header, \
    kernel_interupt, cut_cell, run_cells_below, add_cell, move_cell_up, \
    move_cell_down, kernel_execute

_mypath = os.path.dirname(__file__)
try:
    _MainWindowUI, _MainWindow = \
        uic.loadUiType(os.path.join(_mypath, "main.ui"))
except:
    logging.exception("Failed to load UI files.")
    # This lets the file still be imported, but you won't be able to use it
    class _MainWindowUI(object):
        pass
    class _MainWindow(object):
        pass

[docs]def get_mainwindow_nb(model=None, model_str="model", show=True): """ Create a UI MainWindow to be used with a Jupyter notebook. Args: model: A Pyomo model to work with model_str: A string that is the model variable in the notebook show: show the window after it is created """ ui = MainWindow(model=model, model_str=model_str) get_ipython().events.register('post_execute', ui.refresh_on_execute) if show: ui.show() return ui
[docs]def setup_environment(kexec=True): """ Setup the standard environment """ lines = [ "import logging", "import pandas as pd", "from IPython.display import display, Markdown", "import pyomo.environ as pe", "from pyomo.environ import SolverFactory, TransformationFactory", "from idaes.core.util.model_serializer import from_json, to_json", "import idaes.core.util.model_serializer as model_serial", "from idaes.ui.ui import get_mainwindow_nb", "import idaes.ui.report as rpt", "from idaes.core.util import make_stream_table", "%gui qt"] if kexec: # keeping this for now so I don't break anyone else's stuff for l in lines: kernel_execute(l) else: del lines[-1] lines = "\n".join(lines) print(lines) return(lines)
[docs]class UISetup(QtCore.QObject): updated = QtCore.pyqtSignal() def __init__(self, model=None, model_str="model"): """ This class holds the basic UI setup Args: model: The Pyomo model to view model_str: The model string to use when writing commands """ super(UISetup, self).__init__() self._model = None self._model_str = None self._begin_update = False self.begin_update() self.model = model self.model_str = model_str self.end_update()
[docs] def begin_update(self): """ Lets the model setup be changed without emitting the updated signal until the end_update function is called. """ self._begin_update = True
[docs] def end_update(self, noemit=False): """ Start automatically emitting update signal again when properties are changed and emit update for changes made between begin_update and end_update """ self._begin_update = False if not noemit: self.emit_update()
[docs] def emit_update(self): if not self._begin_update: self.updated.emit()
@property def model(self): return self._model @model.setter def model(self, value): self._model = value self.emit_update() @property def model_str(self): return self._model_str @model_str.setter def model_str(self, value): self._model_str = value self.emit_update()
[docs]class MainWindow(_MainWindow, _MainWindowUI): def __init__(self, *args, **kwargs): model = self.model = kwargs.pop("model", None) model_str = self.model = kwargs.pop("model_str", "model") flags = kwargs.pop("flags", 0) if kwargs.pop("ontop", False): kwargs[flags] = flags | QtCore.Qt.WindowStaysOnTopHint self.ui_setup = UISetup(model=model, model_str=model_str) self.ui_setup.updated.connect(self.update_model) super(MainWindow, self).__init__(*args, **kwargs) self.setupUi(self) # Create model browsers these are dock widgets uis = self.ui_setup self.vars = ModelBrowser(parent=self, standard="Var", ui_setup=uis) self.cons = ModelBrowser(parent=self, standard="Constraint", ui_setup=uis) self.params = ModelBrowser(parent=self, standard="Param", ui_setup=uis) self.exprs = ModelBrowser(parent=self, standard="Expression", ui_setup=uis) # Dock the wigetes allong the bottom and tabify them self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.vars) self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.cons) self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.params) self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.exprs) self.tabifyDockWidget(self.vars, self.params) self.tabifyDockWidget(self.vars, self.exprs) self.tabifyDockWidget(self.vars, self.cons) self.vars.raise_() self.flowsheet = DrawFlowsheet(parent=self, ui_setup=uis) self.layout_tab_0.addWidget(self.flowsheet) # Set menu actions (rembeber the menu items are defined in the ui file) # you can edit the menus in qt-designer self.actionSave_Model_State.triggered.connect(self.save_state_action) self.actionLoad_Model_State.triggered.connect(self.load_state_action) self.action_Save.triggered.connect(save_notebook) self.action_Exit.triggered.connect(self.exit_action) self.action_toggle_variables.triggered.connect(self.vars.toggle) self.action_toggle_constraints.triggered.connect(self.cons.toggle) self.action_toggle_parameters.triggered.connect(self.params.toggle) self.action_toggle_expressions.triggered.connect(self.exprs.toggle) self.action_toggle_jupyter_header.triggered.connect(toggle_jupyter_header) self.actionToggle_Always_on_Top.triggered.connect(self.toggle_always_on_top) self.actionInformation.triggered.connect(self.model_information) self.actionCalculateConstraints.triggered.connect(self.cons.calculate_all) self.actionCalculateExpressions.triggered.connect(self.exprs.calculate_all)
[docs] def update_model(self): pass
[docs] def model_information(self): """ Put some useful model information into a message box Displays: * number of active equality constraints * number of free variables in equality constraints * degrees of freedom Other things that could be added * number of deactivated equalities * number of active inequlaity constraints * number of deactivated inequality constratins * number of free variables not appearing in active constraints * number of fixed variables not appearing in active constraints * number of free variables not appearing in any constraints * number of fixed variables not appearing in any constraints * number of fixed variables appearing in constraints """ active_eq = rpt.count_equality_constraints(self.ui_setup.model) free_vars = rpt.count_free_variables(self.ui_setup.model) dof = free_vars - active_eq if dof == 1: doftext = "Degree" else: doftext = "Degrees" QMessageBox.information( self, "Model Information", "{} -- Active equalities\n" "{} -- Free variables in active equalities\n" "{} -- {} of freedom\n"\ .format(active_eq, free_vars, dof, doftext))
[docs] def toggle_always_on_top(self): """ This toggles the always on top hint. Weather this has any effect after the main window is created probably depends on the system. """ self.setWindowFlags(window.windowFlags() ^ QtCore.Qt.WindowStaysOnTopHint)
[docs] def refresh_on_execute(self): """ This is the call back function that happens when code is executed in the ipython kernel. The main purpose of this right now it to refresh the UI display so that it matches the current state of the model. """ self.vars.refresh() self.cons.refresh() self.exprs.refresh() self.params.refresh()
[docs] def save_state_action(self): """ File dialog to save the model state to json file """ f = QFileDialog.getSaveFileName(self, "Save model state", "", "*.json;; *.*") if f and len(f[0]) > 0: if f[0].endswith(f[1][1:]): f = f[0] else: f = "".join([f[0], f[1][1:]]) insert_jupyter_code(code='tmp_json = to_json({}, fname="{}")'\ .format(self.ui_setup.model_str, f))
[docs] def load_state_action(self): """ File dialog to load the model state from json file """ f = QFileDialog.getOpenFileName( self, "Load model state", "", "*.json;; *.*") if f and len(f[0]) > 0: f = f[0] insert_jupyter_code(code='from_json({}, fname="{}")'\ .format(self.ui_setup.model_str, f))
[docs] def exit_action(self): """ Selecting exit from the UI, triggers the close event on this mainwindow """ self.close()
[docs] def closeEvent(self, event): """ Handel the colse event by asking for confirmation """ result = QMessageBox.question(self, "Exit?", "Are you sure you want to exit ?", QMessageBox.Yes| QMessageBox.No) if result == QMessageBox.Yes: event.accept() else: event.ignore()