diff --git a/foqus_lib/framework/surrogate/ACOSSO.py b/foqus_lib/framework/surrogate/ACOSSO.py index 7809f963b..9a4b490e7 100644 --- a/foqus_lib/framework/surrogate/ACOSSO.py +++ b/foqus_lib/framework/surrogate/ACOSSO.py @@ -18,7 +18,7 @@ beginning of the file (see pluginSearch.plugins() for exact character count of text). They also need to have a .py extension and inherit the surrogate class. -* Plugin wprapper for the ACOSSO surrogate model builder. +* Plugin wrapper for the ACOSSO surrogate model builder. * ACOSSO is executed in R and a working R install with the quadprog package is required. The user must install R * ACOSSO Ref: diff --git a/foqus_lib/gui/flowsheet/nodePanel.py b/foqus_lib/gui/flowsheet/nodePanel.py index 5269010cf..e984ec93c 100644 --- a/foqus_lib/gui/flowsheet/nodePanel.py +++ b/foqus_lib/gui/flowsheet/nodePanel.py @@ -19,14 +19,24 @@ """ import ast import os +import re import platform import types from configparser import RawConfigParser from io import StringIO +from shutil import copyfile +import time from PyQt5 import QtCore, uic from PyQt5.QtGui import QColor -from PyQt5.QtWidgets import QAbstractItemView, QInputDialog, QLineEdit, QMessageBox +from PyQt5.QtWidgets import ( + QAbstractItemView, + QInputDialog, + QLineEdit, + QMessageBox, + QFileDialog, + QTableWidgetItem, +) import foqus_lib.gui.helpers.guiHelpers as gh from foqus_lib.framework.graph.node import * @@ -43,6 +53,13 @@ class nodeDock(_nodeDock, _nodeDockUI): waiting = QtCore.pyqtSignal() # indicates a task is going take a while notwaiting = QtCore.pyqtSignal() # indicates a wait is over + timeoutRow = 0 + setupRow = 1 + initRow = 2 + + valueCol = 0 + descriptionCol = 1 + def __init__(self, dat, parent=None): """ Node view/edit dock widget constructor @@ -80,6 +97,14 @@ def __init__(self, dat, parent=None): self.nodeNameBox.setEditable(False) self.synhi = PythonHighlighter(self.pyCode.document()) self.sim_mapping = None + self.configFileBrowse_button.clicked.connect(self.loadConfigFile) + self.config_file = None + self.vals = None + self.starts = None + self.ends = None + self.updateConfigTable() + self.updateConfig_button.setEnabled(False) + self.updateConfig_button.clicked.connect(self.updateConfigFile) def changeNode(self, index): newNode = self.nodeNameBox.currentText() @@ -787,3 +812,144 @@ def closeEvent(self, event): self.applyChanges() event.accept() self.mw.toggleNodeEditorAction.setChecked(False) + + def loadConfigFile(self): + + # Get file name + if platform.system() == "Windows": + _allFiles = "*.*" + else: + _allFiles = "*" + fileName, selectedFilter = QFileDialog.getOpenFileName( + self, + "Open Configuration File", + "", + "Extensible Markup Language (XML) (*.xml)", + ) + if len(fileName) == 0: + return + + if fileName.endswith(".xml"): + try: + with open(fileName, "r", encoding="utf-8") as file: + self.config_file = file.read() + self.configFile_edit.setText(fileName) + self.updateConfigTable() + except Exception as e: + QMessageBox.critical( + self, + "File Error", + f"Error: {e}", + ) + + else: + QMessageBox.critical( + self, + "Incorrect file extension", + "Please make sure an XML file with Aspen Consumer Configuration " + "information has been selected.", + ) + + def updateConfigTable(self): + if self.config_file is not None: + timeout_text = '' + setup_text = '' + init_text = '' + text_dict = { + "timeout": timeout_text, + "setup": setup_text, + "init": init_text, + } + else: + return + + def get_config_vals(text_dictionary): + vals = {"timeout": None, "setup": None, "init": None} + starts = {"timeout": None, "setup": None, "init": None} + ends = {"timeout": None, "setup": None, "init": None} + for key, text in text_dictionary.items(): + pattern = re.compile(rf"{re.escape(text)}\s*(.*?)") + matches = pattern.finditer(self.config_file) + + for match in matches: + start_index = match.start(1) + end_index = match.end(1) + current_val = self.config_file[start_index:end_index] + starts[key] = start_index + ends[key] = end_index + vals[key] = current_val + + return vals, starts, ends + + current_vals, current_starts, current_ends = get_config_vals(text_dict) + + self.vals = current_vals + self.starts = current_starts + self.ends = current_ends + + # Update table rows + for row, key in enumerate(current_vals): + self.updateConfigTableRow(row, current_vals[key]) + + self.updateConfig_button.setEnabled(True) + + def updateConfigTableRow(self, row, text): + item = self.configTable.item(row, self.valueCol) + if item is None: + item = QTableWidgetItem() + item.setText(text) + item.setTextAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + flags = item.flags() + mask = QtCore.Qt.ItemIsEnabled + item.setFlags(flags | mask) + self.configTable.setItem(row, self.valueCol, item) + + def updateConfigFile(self): + # Backup current XML file + config_file, ext = os.path.splitext(self.configFile_edit.text()) + backup_file = config_file + "-backup-" + str(time.time()) + ext + copyfile(self.configFile_edit.text(), backup_file) + + # Get values from table + new_vals = { + "timeout": self.configTable.item(self.timeoutRow, self.valueCol).text(), + "setup": self.configTable.item(self.setupRow, self.valueCol).text(), + "init": self.configTable.item(self.initRow, self.valueCol).text(), + } + + # Perform the replacement + content = self.config_file + extra_idx = {"timeout": 0, "setup": 0, "init": 0} + + timeout_digits_diff = int(math.log10(int(new_vals["timeout"]))) - int( + math.log10(int(self.vals["timeout"])) + ) + extra_idx["setup"] = timeout_digits_diff + + setup_digits_diff = int(math.log10(int(new_vals["setup"]))) - int( + math.log10(int(self.vals["setup"])) + ) + extra_idx["init"] = timeout_digits_diff + setup_digits_diff + + for key in new_vals: + # Insert new_text at start_index + content = ( + content[: self.starts[key] + extra_idx[key]] + + str(new_vals[key]) + + content[self.ends[key] + extra_idx[key] :] + ) + + # Write the modified content back to the file + with open(self.configFile_edit.text(), "w", encoding="utf-8") as file: + file.write(content) + + msgBox = QMessageBox() + msgBox.setWindowTitle("Aspen Consumer Configuration File") + dirname = os.path.dirname(self.configFile_edit.text()) + msgBox.setText( + "The Aspen Consumer configuration file has been successfully updated.\n" + "\nPlease make sure your changes are correct before loading it into Aspen.\n" + "\nA backup of the old version has been saved in the directory below:\n" + "\n" + dirname + ) + msgBox.exec_() diff --git a/foqus_lib/gui/flowsheet/nodePanel_UI.ui b/foqus_lib/gui/flowsheet/nodePanel_UI.ui index 12e7364c5..7388cf767 100644 --- a/foqus_lib/gui/flowsheet/nodePanel_UI.ui +++ b/foqus_lib/gui/flowsheet/nodePanel_UI.ui @@ -6,8 +6,8 @@ 0 0 - 1138 - 758 + 3440 + 1387 @@ -381,7 +381,7 @@ DMF Server - + ML_AI @@ -441,15 +441,15 @@ - 1 + 0 0 0 - 1096 - 385 + 3370 + 367 @@ -749,8 +749,8 @@ 0 0 - 1096 - 385 + 3370 + 367 @@ -924,8 +924,8 @@ 0 0 - 98 - 89 + 3370 + 879 @@ -953,8 +953,175 @@ + + + + 0 + 0 + 3370 + 367 + + + + Aspen Configuration Parameters + + + + + + + + Configuration File + + + + + + + \Program Files (x86)\Turbine\Lite\Consumers\AspenSinterConsumerConsole.xml + + + + + + + Browse... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + TimeOutIterations + + + + + TimeOutSetupIterations + + + + + TimePostInitIterations + + + + + Value + + + + + Description + + + + + + + + ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable + + + + + + + + ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + + + + ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable + + + + + + + + ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + + + + ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable + + + + + + + + ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + + + + + + Update Configuration Parameters + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + +